sinatra-1.4.3/0000755000004100000410000000000012161612727013215 5ustar www-datawww-datasinatra-1.4.3/README.hu.md0000644000004100000410000004375112161612727015121 0ustar www-datawww-data# Sinatra *Fontos megjegyzés: Ez a dokumentum csak egy fordítása az angol nyelvű változat, és lehet, hogy nem naprakész.* A Sinatra egy [DSL](http://en.wikipedia.org/wiki/Domain-specific_language) webalkalmazások Ruby nyelven történő fejlesztéséhez, minimális energiabefektetéssel: ```ruby # myapp.rb require 'sinatra' get '/' do 'Helló Világ!' end ``` Telepítsd a gem-et és indítsd el az alkalmazást a következőképpen: ```ruby sudo gem install sinatra ruby myapp.rb ``` Az alkalmazás elérhető lesz itt: `http://localhost:4567` ## Útvonalak (routes) A Sinatrában az útvonalat egy HTTP metódus és egy URL-re illeszkedő minta párosa alkotja. Minden egyes útvonalhoz tartozik egy blokk: ```ruby get '/' do .. megjelenítünk valamit .. end post '/' do .. létrehozunk valamit .. end put '/' do .. frissítünk valamit .. end delete '/' do .. törlünk valamit .. end ``` Az útvonalak illeszkedését a rendszer a definiálásuk sorrendjében ellenőrzi. Sorrendben mindig az első illeszkedő útvonalhoz tartozó metódus kerül meghívásra. Az útvonalminták tartalmazhatnak paramétereket is, melyeket a `params` hash-ből érhetünk el: ```ruby get '/hello/:name' do # illeszkedik a "GET /hello/foo" és a "GET /hello/bar" útvonalakra # ekkor params[:name] értéke 'foo' vagy 'bar' lesz "Helló #{params[:name]}!" end ``` A kulcsszavas argumentumokat (named parameters) blokk paraméterek útján is el tudod érni: ```ruby get '/hello/:name' do |n| "Helló #{n}!" end ``` Az útvonalmintákban szerepelhetnek joker paraméterek is, melyeket a `params[:splat]` tömbön keresztül tudunk elérni. ```ruby get '/say/*/to/*' do # illeszkedik a /say/hello/to/world mintára params[:splat] # => ["hello", "world"] end get '/download/*.*' do # illeszkedik a /download/path/to/file.xml mintára params[:splat] # => ["path/to/file", "xml"] end ``` Reguláris kifejezéseket is felvehetünk az útvonalba: ```ruby get %r{/hello/([\w]+)} do "Helló, #{params[:captures].first}!" end ``` Vagy blokk paramétereket: ```ruby get %r{/hello/([\w]+)} do |c| "Helló, #{c}!" end ``` Az útvonalak azonban számos egyéb illeszkedési feltétel szerint is tervezhetők, így például az user agent karakterláncot alapul véve: ```ruby get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do "A Songbird #{params[:agent][0]} verzióját használod" end get '/foo' do # illeszkedik az egyéb user agentekre end ``` ## Statikus állományok A statikus fájlok kiszolgálása a `./public` könyvtárból történik, de természetesen más könyvtárat is megadhatsz erre a célra, mégpedig a :public_folder kapcsoló beállításával: set :public_folder, File.dirname(__FILE__) + '/static' Fontos mgejegyezni, hogy a nyilvános könyvtár neve nem szerepel az URL-ben. A ./public/css/style.css fájl az `http://example.com/css/style.css` URL-en lesz elérhető. ## Nézetek és Sablonok A sablonfájlokat rendszerint a `./views` könyvtárba helyezzük, de itt is lehetőség nyílik egyéb könyvtár használatára: set :views, File.dirname(__FILE__) + '/templates' Nagyon fontos észben tartani, hogy a sablononkra mindig szimbólumokkal hivatkozunk, még akkor is, ha egyéb (ebben az esetben a :'subdir/template') könyvtárban tároljuk őket. A renderelő metódusok minden, nekik közvetlenül átadott karakterláncot megjelenítenek. ### Haml sablonok HAML sablonok rendereléséhez szükségünk lesz a haml gem-re vagy könyvtárra: ```ruby # Importáljuk be a haml-t az alkalmazásba require 'haml' get '/' do haml :index end ``` Ez szépen lerendereli a `./views/index.haml` sablont. A [Haml kapcsolói](http://haml.hamptoncatlin.com/docs/rdoc/classes/Haml.html) globálisan is beállíthatók a Sinatra konfigurációi között, lásd az [Options and Configurations](http://www.sinatrarb.com/configuration.html) lapot. A globális beállításokat lehetőségünk van felülírni metódus szinten is. ```ruby set :haml, {:format => :html5 } # az alapértelmezett Haml formátum az :xhtml get '/' do haml :index, :haml_options => {:format => :html4 } # immár felülírva end ``` ### Erb sablonok # Importáljuk be az erb-t az alkalmazásba ```ruby require 'erb' get '/' do erb :index end ``` Ez a `./views/index.erb` sablont fogja lerenderelni. ### Builder sablonok Szükségünk lesz a builder gem-re vagy könyvtárra a builder sablonok rendereléséhez: # Importáljuk be a builder-t az alkalmazásba ```ruby require 'builder' get '/' do builder :index end ``` Ez pedig a `./views/index.builder` állományt fogja renderelni. ### Sass sablonok Sass sablonok használatához szükség lesz a haml gem-re vagy könyvtárra: # Be kell importálni a haml, vagy a sass könyvtárat ```ruby require 'sass' get '/stylesheet.css' do sass :stylesheet end ``` Így a `./views/stylesheet.sass` fájl máris renderelhető. A [Sass kapcsolói](http://haml.hamptoncatlin.com/docs/rdoc/classes/Sass.html) globálisan is beállíthatók a Sinatra konfigurációi között, lásd az [Options and Configurations](http://www.sinatrarb.com/configuration.html) lapot. A globális beállításokat lehetőségünk van felülírni metódus szinten is. ```ruby set :sass, {:style => :compact } # az alapértelmezett Sass stílus a :nested get '/stylesheet.css' do sass :stylesheet, :sass_options => {:style => :expanded } # felülírva end ``` ### Beágyazott sablonok ```ruby get '/' do haml '%div.title Helló Világ' end ``` Lerendereli a beágyazott sablon karakerláncát. ### Változók elérése a sablonokban A sablonok ugyanabban a kontextusban kerülnek kiértékelésre, mint az útvonal metódusok (route handlers). Az útvonal metódusokban megadott változók közvetlenül elérhetőek lesznek a sablonokban: ```ruby get '/:id' do @foo = Foo.find(params[:id]) haml '%h1= @foo.name' end ``` De megadhatod egy lokális változókat tartalmazó explicit hash-ben is: ```ruby get '/:id' do foo = Foo.find(params[:id]) haml '%h1= foo.name', :locals => { :foo => foo } end ``` Ezt leginkább akkor érdemes megtenni, ha partial-eket akarunk renderelni valamely más sablonból. ### Fájlon belüli sablonok Sablonokat úgy is megadhatunk, hogy egyszerűen az alkalmazás fájl végére begépeljük őket: ```ruby require 'rubygems' require 'sinatra' get '/' do haml :index end __END__ @@ layout %html = yield @@ index %div.title Helló Világ!!!!! ``` Megjegyzés: azok a fájlon belüli sablonok, amelyek az alkalmazás fájl végére kerülnek és függnek a sinatra könyvtártól, automatikusan betöltődnek. Ha ugyanezt más alkalmazásfájlban is szeretnéd megtenni, hívd meg a use_in_file_templates! metódust az adott fájlban. ### Kulcsszavas sablonok Sablonokat végül a felsőszintű template metódussal is definiálhatunk: ```ruby template :layout do "%html\n =yield\n" end template :index do '%div.title Helló Világ!' end get '/' do haml :index end ``` Ha létezik "layout" nevű sablon, akkor az minden esetben meghívódik, amikor csak egy sablon renderelésre kerül. A layoutokat ki lehet kapcsolni a `:layout => false` meghívásával. ```ruby get '/' do haml :index, :layout => !request.xhr? end ``` ## Helperek Használd a felső szintű helpers metódust azokhoz a helper függvényekhez, amiket az útvonal metódusokban és a sablonokban akarsz használni: ```ruby helpers do def bar(name) "#{name}bar" end end get '/:name' do bar(params[:name]) end ``` ## Szűrők (filters) Az előszűrők (before filter) az adott hívás kontextusában minden egyes kérés alkalmával kiértékelődnek, így módosíthatják a kérést és a választ egyaránt. A szűrőkbe felvett példányváltozók elérhetőek lesznek az útvonalakban és a sablonokban is: ```ruby before do @note = 'Csá!' request.path_info = '/foo/bar/baz' end get '/foo/*' do @note #=> 'Szeva!' params[:splat] #=> 'bar/baz' end ``` Az utószűrők az egyes kérések után, az adott kérés kontextusában kerülnek kiértékelésre, így ezek is képesek módosítani a kérést és a választ egyaránt. Az előszűrőkben és úvonalakban létrehozott példányváltozók elérhetőek lesznek az utószűrők számára: ```ruby after do puts response.status end ``` ## Megállítás Egy kérés szűrőben vagy útvonalban történő azonnal blokkolásához használd a következő parancsot: halt A megállításkor egy blokktörzset is megadhatsz ... halt 'ez fog megjelenni a törzsben' Vagy állítsd be a HTTP státuszt és a törzset is egyszerre ... halt 401, 'menj innen!' ## Passzolás Az útvonalak továbbadhatják a végrehajtást egy másik útvonalnak a `pass` függvényhívással: ```ruby get '/guess/:who' do pass unless params[:who] == 'Frici' "Elkaptál!" end get '/guess/*' do "Elhibáztál!" end ``` Az útvonal blokkja azonnal kilép és átadja a vezérlést a következő illeszkedő útvonalnak. Ha nem talál megfelelő útvonalat, a Sinatra egy 404-es hibával tér vissza. ## Beállítások Csak indításkor, de minden környezetre érvényesen fusson le: ```ruby configure do ... end ``` Csak akkor fusson le, ha a környezet (a RACK_ENV környezeti változóban) `:production`-ra van állítva: ```ruby configure :production do ... end ``` Csak akkor fusson le, ha a környezet :production vagy :test: ```ruby configure :production, :test do ... end ``` ## Hibakezelés A hibakezelők ugyanabban a kontextusban futnak le, mint az útvonalak és előszűrők, ezért számukra is elérhetőek mindazok a könyvtárak, amelyek az utóbbiak rendelkezésére is állnak; így például a `haml`, az `erb`, a `halt` stb. ### Nem található Amikor a `Sinatra::NotFound` kivétel fellép, vagy a válasz HTTP státuszkódja 404-es, mindig a `not_found` metódus hívódik meg. ```ruby not_found do 'Sehol sem találom, amit keresel' end ``` ### Hiba Az `error` metódus hívódik meg olyankor, amikor egy útvonal, blokk vagy előszűrő kivételt vált ki. A kivétel objektum lehívható a `sinatra.error` Rack változótól: ```ruby error do 'Elnézést, de valami szörnyű hiba lépett fel - ' + env['sinatra.error'].name end ``` Egyéni hibakezelés: ```ruby error MyCustomError do 'Szóval az van, hogy...' + env['sinatra.error'].message end ``` És amikor fellép: ```ruby get '/' do raise MyCustomError, 'valami nem stimmel!' end ``` Ez fog megjelenni: Szóval az van, hogy... valami nem stimmel! A Sinatra speciális `not_found` és `error` hibakezelőket használ, amikor a futtatási környezet fejlesztői módba van kapcsolva. ## Mime típusok A `send_file` metódus használatakor, vagy statikus fájlok kiszolgálásakor előfordulhat, hogy a Sinatra nem ismeri fel a fájlok mime típusát. Ilyenkor használd a +mime_type+ kapcsolót a fájlkiterjesztés bevezetéséhez: ```ruby mime_type :foo, 'text/foo' ``` ## Rack Middleware A Sinatra egy Ruby keretrendszerek számára kifejlesztett egyszerű és szabványos interfészre, a [Rack](http://rack.rubyforge.org/) -re épül. A Rack fejlesztői szempontból egyik legérdekesebb jellemzője, hogy támogatja az úgynevezett "middleware" elnevezésű komponenseket, amelyek beékelődnek a szerver és az alkalmazás közé, így képesek megfigyelni és/vagy módosítani a HTTP kéréseket és válaszokat. Segítségükkel különféle, egységesen működő funkciókat építhetünk be rendszerünkbe. A Sinatra keretrendszerben gyerekjáték a Rack middleware-ek behúzása a `use` metódus segítségével: ```ruby require 'sinatra' require 'my_custom_middleware' use Rack::Lint use MyCustomMiddleware get '/hello' do 'Helló Világ' end ``` A `use` metódus szemantikája megegyezik a [Rack::Builder](http://rack.rubyforge.org/doc/classes/Rack/Builder.html) DSL-ben használt +use+ metóduséval (az említett DSL-t leginkább rackup állományokban használják). Hogy egy példát említsünk, a `use` metódus elfogad változókat és blokkokat egyaránt, akár kombinálva is ezeket: ```ruby use Rack::Auth::Basic do |username, password| username == 'admin' && password == 'titkos' end ``` A Rack terjesztéssel egy csomó alap middleware komponens is érkezik, amelyekkel a naplózás, URL útvonalak megadása, autentikáció és munkamenet-kezelés könnyen megvalósítható. A Sinatra ezek közül elég sokat automatikusan felhasznál a beállításoktól függően, így ezek explicit betöltésével (+use+) nem kell bajlódnod. ## Tesztelés Sinatra teszteket bármely Rack alapú tesztelő könyvtárral vagy keretrendszerrel készíthetsz. Mi a [Rack::Test](http://gitrdoc.com/brynary/rack-test) könyvtárat ajánljuk: ```ruby require 'my_sinatra_app' require 'rack/test' class MyAppTest < Test::Unit::TestCase include Rack::Test::Methods def app Sinatra::Application end def test_my_default get '/' assert_equal 'Helló Világ!', last_response.body end def test_with_params get '/meet', :name => 'Frici' assert_equal 'Helló Frici!', last_response.body end def test_with_rack_env get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' assert_equal "Songbird-öt használsz!", last_response.body end end ``` Megjegyzés: A beépített Sinatra::Test és Sinatra::TestHarness osztályok a 0.9.2-es kiadástól kezdve elavultnak számítanak. ## Sinatra::Base - Middleware-ek, könyvtárak és moduláris alkalmazások Az alkalmazást felső szinten építeni megfelelhet mondjuk egy kisebb app esetén, ám kifejezetten károsnak bizonyulhat olyan komolyabb, újra felhasználható komponensek készítésekor, mint például egy Rack middleware, Rails metal, egyszerűbb kiszolgáló komponenssel bíró könyvtárak vagy éppen Sinatra kiterjesztések. A felső szintű DSL bepiszkítja az Objektum névteret, ráadásul kisalkalmazásokra szabott beállításokat feltételez (így például egyetlen alkalmazásfájl, `./public` és `./views` könyvtár meglétét, naplózást, kivételkezelő oldalt stb.). Itt jön a képbe a Sinatra::Base osztály: ```ruby require 'sinatra/base' class MyApp < Sinatra::Base set :sessions, true set :foo, 'bar' get '/' do 'Helló Világ!' end end ``` A MyApp osztály immár önálló Rack komponensként, mondjuk Rack middleware-ként vagy alkalmazásként, esetleg Rails metal-ként is tud működni. Közvetlenül használhatod (`use`) vagy futtathatod (`run`) az osztályodat egy rackup konfigurációs állományban (`config.ru`), vagy egy szerverkomponenst tartalmazó könyvtár vezérlésekor: ```ruby MyApp.run! :host => 'localhost', :port => 9090 ``` A Sinatra::Base gyermekosztályaiban elérhető metódusok egyúttal a felső szintű DSL-en keresztül is hozzáférhetők. A legtöbb felső szintű alkalmazás átalakítható Sinatra::Base alapú komponensekké két lépésben: * A fájlban nem a `sinatra`, hanem a `sinatra/base` osztályt kell beimportálni, mert egyébként az összes Sinatra DSL metódus a fő névtérbe kerül. * Az alkalmazás útvonalait, hibakezelőit, szűrőit és beállításait a Sinatra::Base osztály gyermekosztályaiban kell megadni. A `Sinatra::Base` osztály igazából egy üres lap: a legtöbb funkció alapból ki van kapcsolva, beleértve a beépített szervert is. A beállításokkal és az egyes kapcsolók hatásával az [Options and Configuration](http://sinatra.github.com/configuration.html) lap foglalkozik. Széljegyzet: A Sinatra felső szintű DSL-je egy egyszerű delegációs rendszerre épül. A Sinatra::Application osztály - a Sinatra::Base egy speciális osztályaként - fogadja az összes :get, :put, :post, :delete, :before, :error, :not_found, :configure és :set üzenetet, ami csak a felső szintre beérkezik. Érdemes utánanézned a kódban, miképp [kerül be](http://github.com/sinatra/sinatra/blob/master/lib/sinatra/main.rb#L25) a [Sinatra::Delegator mixin](http://github.com/sinatra/sinatra/blob/master/lib/sinatra/base.rb#L1064) a fő névtérbe. ## Parancssori lehetőségek Sinatra alkalmazásokat közvetlenül futtathatunk: ``` ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-s HANDLER] ``` Az alábbi kapcsolókat ismeri fel a rendszer: -h # segítség -p # a port beállítása (alapértelmezés szerint ez a 4567-es) -e # a környezet beállítása (alapértelmezés szerint ez a development) -s # a rack szerver/handler beállítása (alapértelmezetten ez a thin) -x # a mutex lock bekapcsolása (alapértelmezetten ki van kapcsolva) ## Fejlesztői változat Ha a Sinatra legfrissebb, fejlesztői változatát szeretnéd használni, készíts egy helyi másolatot és indítsd az alkalmazásodat úgy, hogy a `sinatra/lib` könyvtár elérhető legyen a `LOAD_PATH`-on: ``` cd myapp git clone git://github.com/sinatra/sinatra.git ruby -Isinatra/lib myapp.rb ``` De hozzá is adhatod a sinatra/lib könyvtárat a LOAD_PATH-hoz az alkalmazásodban: ```ruby $LOAD_PATH.unshift File.dirname(__FILE__) + '/sinatra/lib' require 'rubygems' require 'sinatra' get '/about' do "A következő változatot futtatom " + Sinatra::VERSION end ``` A Sinatra frissítését később így végezheted el: ``` cd myproject/sinatra git pull ``` ## További információk * [A projekt weboldala](http://sinatra.github.com/) - Kiegészítő dokumentáció, hírek, hasznos linkek * [Közreműködés](http://sinatra.github.com/contributing.html) - Hibát találtál? Segítségre van szükséged? Foltot küldenél be? * [Lighthouse](http://sinatra.lighthouseapp.com) - Hibakövetés és kiadások * [Twitter](http://twitter.com/sinatra) * [Levelezőlista](http://groups.google.com/group/sinatrarb) * [IRC: #sinatra](irc://chat.freenode.net/#sinatra) a http://freenode.net címen sinatra-1.4.3/test/0000755000004100000410000000000012161612727014174 5ustar www-datawww-datasinatra-1.4.3/test/haml_test.rb0000644000004100000410000000564512161612727016513 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) begin require 'haml' class HAMLTest < Test::Unit::TestCase def haml_app(&block) mock_app do set :views, File.dirname(__FILE__) + '/views' get('/', &block) end get '/' end it 'renders inline HAML strings' do haml_app { haml '%h1 Hiya' } assert ok? assert_equal "

Hiya

\n", body end it 'renders .haml files in views path' do haml_app { haml :hello } assert ok? assert_equal "

Hello From Haml

\n", body end it "renders with inline layouts" do mock_app do layout { %q(%h1= 'THIS. IS. ' + yield.upcase) } get('/') { haml '%em Sparta' } end get '/' assert ok? assert_equal "

THIS. IS. SPARTA

\n", body end it "renders with file layouts" do haml_app { haml 'Hello World', :layout => :layout2 } assert ok? assert_equal "

HAML Layout!

\n

Hello World

\n", body end it "raises error if template not found" do mock_app { get('/') { haml :no_such_template } } assert_raise(Errno::ENOENT) { get('/') } end it "passes HAML options to the Haml engine" do mock_app { get('/') { haml "!!!\n%h1 Hello World", :format => :html5 } } get '/' assert ok? assert_equal "\n

Hello World

\n", body end it "passes default HAML options to the Haml engine" do mock_app do set :haml, {:format => :html5} get('/') { haml "!!!\n%h1 Hello World" } end get '/' assert ok? assert_equal "\n

Hello World

\n", body end it "merges the default HAML options with the overrides and passes them to the Haml engine" do mock_app do set :haml, {:format => :html5, :attr_wrapper => '"'} # default HAML attr are get('/') { haml "!!!\n%h1{:class => :header} Hello World" } get('/html4') { haml "!!!\n%h1{:class => 'header'} Hello World", :format => :html4 } end get '/' assert ok? assert_equal "\n

Hello World

\n", body get '/html4' assert ok? assert_match(/^ { :foo => 'bar' }} assert_equal "bar\n", body end it "can render truly nested layouts by accepting a layout and a block with the contents" do mock_app do template(:main_outer_layout) { "%h1 Title\n= yield" } template(:an_inner_layout) { "%h2 Subtitle\n= yield" } template(:a_page) { "%p Contents." } get('/') do haml :main_outer_layout, :layout => false do haml :an_inner_layout do haml :a_page end end end end get '/' assert ok? assert_body "

Title

\n

Subtitle

\n

Contents.

\n" end end rescue LoadError warn "#{$!.to_s}: skipping haml tests" end sinatra-1.4.3/test/coffee_test.rb0000644000004100000410000000421612161612727017012 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) begin require 'coffee-script' require 'execjs' begin ExecJS.compile '1' rescue Exception raise LoadError, 'unable to execute JavaScript' end class CoffeeTest < Test::Unit::TestCase def coffee_app(options = {}, &block) mock_app do set :views, File.dirname(__FILE__) + '/views' set(options) get('/', &block) end get '/' end it 'renders inline Coffee strings' do coffee_app { coffee "alert 'Aye!'\n" } assert ok? assert body.include?("alert('Aye!');") end it 'defaults content type to javascript' do coffee_app { coffee "alert 'Aye!'\n" } assert ok? assert_equal "application/javascript;charset=utf-8", response['Content-Type'] end it 'defaults allows setting content type per route' do coffee_app do content_type :html coffee "alert 'Aye!'\n" end assert ok? assert_equal "text/html;charset=utf-8", response['Content-Type'] end it 'defaults allows setting content type globally' do coffee_app(:coffee => { :content_type => 'html' }) do coffee "alert 'Aye!'\n" end assert ok? assert_equal "text/html;charset=utf-8", response['Content-Type'] end it 'renders .coffee files in views path' do coffee_app { coffee :hello } assert ok? assert_include body, "alert(\"Aye!\");" end it 'ignores the layout option' do coffee_app { coffee :hello, :layout => :layout2 } assert ok? assert_include body, "alert(\"Aye!\");" end it "raises error if template not found" do mock_app { get('/') { coffee :no_such_template } } assert_raise(Errno::ENOENT) { get('/') } end it "passes coffee options to the coffee engine" do coffee_app { coffee "alert 'Aye!'\n", :no_wrap => true } assert ok? assert_body "alert('Aye!');" end it "passes default coffee options to the coffee engine" do mock_app do set :coffee, :no_wrap => true # default coffee style is :nested get('/') { coffee "alert 'Aye!'\n" } end get '/' assert ok? assert_body "alert('Aye!');" end end rescue LoadError warn "#{$!.to_s}: skipping coffee tests" end sinatra-1.4.3/test/radius_test.rb0000644000004100000410000000254312161612727017053 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) begin require 'radius' class RadiusTest < Test::Unit::TestCase def radius_app(&block) mock_app do set :views, File.dirname(__FILE__) + '/views' get('/', &block) end get '/' end it 'renders inline radius strings' do radius_app { radius '

Hiya

' } assert ok? assert_equal "

Hiya

", body end it 'renders .radius files in views path' do radius_app { radius :hello } assert ok? assert_equal "

Hello From Radius

\n", body end it "renders with inline layouts" do mock_app do layout { "

THIS. IS.

" } get('/') { radius 'SPARTA' } end get '/' assert ok? assert_equal "

THIS. IS. SPARTA

", body end it "renders with file layouts" do radius_app { radius 'Hello World', :layout => :layout2 } assert ok? assert_equal "

Radius Layout!

\n

Hello World

\n", body end it "raises error if template not found" do mock_app { get('/') { radius :no_such_template } } assert_raise(Errno::ENOENT) { get('/') } end it "allows passing locals" do radius_app { radius '', :locals => { :value => 'foo' } } assert ok? assert_equal 'foo', body end end rescue LoadError warn "#{$!.to_s}: skipping radius tests" end sinatra-1.4.3/test/request_test.rb0000644000004100000410000000501312161612727017247 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) require 'stringio' class RequestTest < Test::Unit::TestCase it 'responds to #user_agent' do request = Sinatra::Request.new({'HTTP_USER_AGENT' => 'Test'}) assert request.respond_to?(:user_agent) assert_equal 'Test', request.user_agent end it 'parses POST params when Content-Type is form-dataish' do request = Sinatra::Request.new( 'REQUEST_METHOD' => 'PUT', 'CONTENT_TYPE' => 'application/x-www-form-urlencoded', 'rack.input' => StringIO.new('foo=bar') ) assert_equal 'bar', request.params['foo'] end it 'is secure when the url scheme is https' do request = Sinatra::Request.new('rack.url_scheme' => 'https') assert request.secure? end it 'is not secure when the url scheme is http' do request = Sinatra::Request.new('rack.url_scheme' => 'http') assert !request.secure? end it 'respects X-Forwarded-Proto header for proxied SSL' do request = Sinatra::Request.new('HTTP_X_FORWARDED_PROTO' => 'https') assert request.secure? end it 'is possible to marshal params' do request = Sinatra::Request.new( 'REQUEST_METHOD' => 'PUT', 'CONTENT_TYPE' => 'application/x-www-form-urlencoded', 'rack.input' => StringIO.new('foo=bar') ) Sinatra::Base.new!.send(:indifferent_hash).replace(request.params) dumped = Marshal.dump(request.params) assert_equal 'bar', Marshal.load(dumped)['foo'] end it "exposes the preferred type's parameters" do request = Sinatra::Request.new( 'HTTP_ACCEPT' => 'image/jpeg; compress=0.25' ) assert_equal({ 'compress' => '0.25' }, request.preferred_type.params) end it "makes accept types behave like strings" do request = Sinatra::Request.new('HTTP_ACCEPT' => 'image/jpeg; compress=0.25') assert request.accept?('image/jpeg') assert_equal 'image/jpeg', request.preferred_type.to_s assert_equal 'image/jpeg', request.preferred_type.to_str assert_equal 'image', request.preferred_type.split('/').first String.instance_methods.each do |method| next unless "".respond_to? method assert request.preferred_type.respond_to?(method), "responds to #{method}" end end it "properly decodes MIME type parameters" do request = Sinatra::Request.new( 'HTTP_ACCEPT' => 'image/jpeg;unquoted=0.25;quoted="0.25";chartest="\";,\x"' ) expected = { 'unquoted' => '0.25', 'quoted' => '0.25', 'chartest' => '";,x' } assert_equal(expected, request.preferred_type.params) end end sinatra-1.4.3/test/encoding_test.rb0000644000004100000410000000116112161612727017345 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path('../helper', __FILE__) require 'erb' class BaseTest < Test::Unit::TestCase setup do @base = Sinatra.new(Sinatra::Base) @base.set :views, File.dirname(__FILE__) + "/views" end it 'allows unicode strings in ascii templates per default (1.9)' do next unless defined? Encoding @base.new!.erb(File.read(@base.views + "/ascii.erb").encode("ASCII"), {}, :value => "åkej") end it 'allows ascii strings in unicode templates per default (1.9)' do next unless defined? Encoding @base.new!.erb(:utf8, {}, :value => "Some Lyrics".encode("ASCII")) end end sinatra-1.4.3/test/integration_helper.rb0000644000004100000410000001307212161612727020406 0ustar www-datawww-datarequire 'sinatra/base' require 'rbconfig' require 'open-uri' require 'net/http' require 'timeout' module IntegrationHelper class BaseServer extend Enumerable attr_accessor :server, :port, :pipe alias name server def self.all @all ||= [] end def self.each(&block) all.each(&block) end def self.run(server, port) new(server, port).run end def app_file File.expand_path('../integration/app.rb', __FILE__) end def environment "development" end def initialize(server, port) @installed, @pipe, @server, @port = nil, nil, server, port Server.all << self end def run return unless installed? kill @log = "" @pipe = IO.popen(command) @started = Time.now warn "#{server} up and running on port #{port}" if ping at_exit { kill } end def ping(timeout = 30) loop do return if alive? if Time.now - @started > timeout $stderr.puts command, log fail "timeout" else sleep 0.1 end end end def alive? 3.times { get('/ping') } true rescue Errno::ECONNREFUSED, Errno::ECONNRESET, EOFError, SystemCallError, OpenURI::HTTPError, Timeout::Error false end def get_stream(url = "/stream", &block) Net::HTTP.start '127.0.0.1', port do |http| request = Net::HTTP::Get.new url http.request request do |response| response.read_body(&block) end end end def get(url) Timeout.timeout(1) { open("http://127.0.0.1:#{port}#{url}").read } end def log @log ||= "" loop { @log << @pipe.read_nonblock(1) } rescue Exception @log end def installed? return @installed unless @installed.nil? s = server == 'HTTP' ? 'net/http/server' : server require s @installed = true rescue LoadError warn "#{server} is not installed, skipping integration tests" @installed = false end def command @command ||= begin cmd = ["RACK_ENV=#{environment}", "exec"] if RbConfig.respond_to? :ruby cmd << RbConfig.ruby.inspect else file, dir = RbConfig::CONFIG.values_at('ruby_install_name', 'bindir') cmd << File.expand_path(file, dir).inspect end cmd << "-w" unless thin? || net_http_server? cmd << "-I" << File.expand_path('../../lib', __FILE__).inspect cmd << app_file.inspect << '-s' << server << '-o' << '127.0.0.1' << '-p' << port cmd << "-e" << environment.to_s << '2>&1' cmd.join " " end end def kill return unless pipe Process.kill("KILL", pipe.pid) rescue NotImplementedError system "kill -9 #{pipe.pid}" rescue Errno::ESRCH end def webrick? name.to_s == "webrick" end def thin? name.to_s == "thin" end def puma? name.to_s == "puma" end def trinidad? name.to_s == "trinidad" end def net_http_server? name.to_s == 'HTTP' end def warnings log.scan(%r[(?:\(eval|lib/sinatra).*warning:.*$]) end def run_test(target, &block) retries ||= 3 target.server = self run unless alive? target.instance_eval(&block) rescue Exception => error retries -= 1 kill retries < 0 ? retry : raise(error) end end if RUBY_ENGINE == "jruby" class JRubyServer < BaseServer def start_vm require 'java' # Create a new container, set load paths and env # SINGLETHREAD means create a new runtime vm = org.jruby.embed.ScriptingContainer.new(org.jruby.embed.LocalContextScope::SINGLETHREAD) vm.load_paths = [File.expand_path('../../lib', __FILE__)] vm.environment = ENV.merge('RACK_ENV' => environment.to_s) # This ensures processing of RUBYOPT which activates Bundler vm.provider.ruby_instance_config.process_arguments [] vm.argv = ['-s', server.to_s, '-o', '127.0.0.1', '-p', port.to_s, '-e', environment.to_s] # Set stdout/stderr so we can retrieve log @pipe = java.io.ByteArrayOutputStream.new vm.output = java.io.PrintStream.new(@pipe) vm.error = java.io.PrintStream.new(@pipe) Thread.new do # Hack to ensure that Kernel#caller has the same info as # when run from command-line, for Sinatra::Application.app_file. # Also, line numbers are zero-based in JRuby's parser vm.provider.runtime.current_context.set_file_and_line(app_file, 0) # Run the app vm.run_scriptlet org.jruby.embed.PathType::ABSOLUTE, app_file # terminate launches at_exit hooks which start server vm.terminate end end def run return unless installed? kill @thread = start_vm @started = Time.now warn "#{server} up and running on port #{port}" if ping at_exit { kill } end def log String.from_java_bytes @pipe.to_byte_array end def kill @thread.kill if @thread @thread = nil end end Server = JRubyServer else Server = BaseServer end def it(message, &block) Server.each do |server| next unless server.installed? super("with #{server.name}: #{message}") { server.run_test(self, &block) } end end def self.extend_object(obj) super base_port = 5000 + Process.pid % 100 Sinatra::Base.server.each_with_index do |server, index| Server.run(server, 5000+index) end end end sinatra-1.4.3/test/delegator_test.rb0000644000004100000410000000743112161612727017533 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) class DelegatorTest < Test::Unit::TestCase class Mirror attr_reader :last_call def method_missing(*a, &b) @last_call = [*a.map(&:to_s)] @last_call << b if b end end def self.delegates(name) it "delegates #{name}" do m = mirror { send name } assert_equal [name.to_s], m.last_call end it "delegates #{name} with arguments" do m = mirror { send name, "foo", "bar" } assert_equal [name.to_s, "foo", "bar"], m.last_call end it "delegates #{name} with block" do block = proc { } m = mirror { send(name, &block) } assert_equal [name.to_s, block], m.last_call end end setup do @target_was = Sinatra::Delegator.target end def teardown Sinatra::Delegator.target = @target_was end def delegation_app(&block) mock_app { Sinatra::Delegator.target = self } delegate(&block) end def mirror(&block) mirror = Mirror.new Sinatra::Delegator.target = mirror delegate(&block) end def delegate(&block) assert Sinatra::Delegator.target != Sinatra::Application Object.new.extend(Sinatra::Delegator).instance_eval(&block) if block Sinatra::Delegator.target end def target Sinatra::Delegator.target end it 'defaults to Sinatra::Application as target' do assert_equal Sinatra::Application, Sinatra::Delegator.target end %w[get put post delete options patch link unlink].each do |verb| it "delegates #{verb} correctly" do delegation_app do send(verb, '/hello') { 'Hello World' } end request = Rack::MockRequest.new(@app) response = request.request(verb.upcase, '/hello', {}) assert response.ok? assert_equal 'Hello World', response.body end end it "delegates head correctly" do delegation_app do head '/hello' do response['X-Hello'] = 'World!' 'remove me' end end request = Rack::MockRequest.new(@app) response = request.request('HEAD', '/hello', {}) assert response.ok? assert_equal 'World!', response['X-Hello'] assert_equal '', response.body end it "registers extensions with the delegation target" do app, mixin = mirror, Module.new Sinatra.register mixin assert_equal ["register", mixin.to_s], app.last_call end it "registers helpers with the delegation target" do app, mixin = mirror, Module.new Sinatra.helpers mixin assert_equal ["helpers", mixin.to_s], app.last_call end it "registers middleware with the delegation target" do app, mixin = mirror, Module.new Sinatra.use mixin assert_equal ["use", mixin.to_s], app.last_call end it "should work with method_missing proxies for options" do mixin = Module.new do def respond_to?(method, *) method.to_sym == :options or super end def method_missing(method, *args, &block) return super unless method.to_sym == :options {:some => :option} end end value = nil mirror do extend mixin value = options end assert_equal({:some => :option}, value) end it "delegates crazy method names" do Sinatra::Delegator.delegate "foo:bar:" method = mirror { send "foo:bar:" }.last_call.first assert_equal "foo:bar:", method end delegates 'get' delegates 'patch' delegates 'put' delegates 'post' delegates 'delete' delegates 'head' delegates 'options' delegates 'template' delegates 'layout' delegates 'before' delegates 'after' delegates 'error' delegates 'not_found' delegates 'configure' delegates 'set' delegates 'mime_type' delegates 'enable' delegates 'disable' delegates 'use' delegates 'development?' delegates 'test?' delegates 'production?' delegates 'helpers' delegates 'settings' end sinatra-1.4.3/test/nokogiri_test.rb0000644000004100000410000000320712161612727017403 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) begin require 'nokogiri' class NokogiriTest < Test::Unit::TestCase def nokogiri_app(&block) mock_app do set :views, File.dirname(__FILE__) + '/views' get('/', &block) end get '/' end it 'renders inline Nokogiri strings' do nokogiri_app { nokogiri 'xml' } assert ok? assert_body %(\n) end it 'renders inline blocks' do nokogiri_app do @name = "Frank & Mary" nokogiri { |xml| xml.couple @name } end assert ok? assert_body %(\nFrank & Mary\n) end it 'renders .nokogiri files in views path' do nokogiri_app do @name = "Blue" nokogiri :hello end assert ok? assert_body "\nYou're my boy, Blue!\n" end it "renders with inline layouts" do next if Tilt::VERSION <= "1.1" mock_app do layout { %(xml.layout { xml << yield }) } get('/') { nokogiri %(xml.em 'Hello World') } end get '/' assert ok? assert_body %(\n\n Hello World\n\n) end it "renders with file layouts" do next if Tilt::VERSION <= "1.1" nokogiri_app { nokogiri %(xml.em 'Hello World'), :layout => :layout2 } assert ok? assert_body %(\n\n Hello World\n\n) end it "raises error if template not found" do mock_app { get('/') { nokogiri :no_such_template } } assert_raise(Errno::ENOENT) { get('/') } end end rescue LoadError warn "#{$!.to_s}: skipping nokogiri tests" end sinatra-1.4.3/test/wlang_test.rb0000644000004100000410000000367112161612727016677 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) begin require 'wlang' class WLangTest < Test::Unit::TestCase def engine Tilt::WLangTemplate end def wlang_app(&block) mock_app { set :views, File.dirname(__FILE__) + '/views' get '/', &block } get '/' end it 'uses the correct engine' do assert_equal engine, Tilt[:wlang] end it 'renders .wlang files in views path' do wlang_app { wlang :hello } assert ok? assert_equal "Hello from wlang!\n", body end it 'renders in the app instance scope' do mock_app do helpers do def who; "world"; end end get('/') { wlang 'Hello +{who}!' } end get '/' assert ok? assert_equal 'Hello world!', body end it 'takes a :locals option' do wlang_app do locals = {:foo => 'Bar'} wlang 'Hello ${foo}!', :locals => locals end assert ok? assert_equal 'Hello Bar!', body end it "renders with inline layouts" do mock_app do layout { 'THIS. IS. +{yield.upcase}!' } get('/') { wlang 'Sparta' } end get '/' assert ok? assert_equal 'THIS. IS. SPARTA!', body end it "renders with file layouts" do wlang_app { wlang 'Hello World', :layout => :layout2 } assert ok? assert_body "WLang Layout!\nHello World" end it "can rendered truly nested layouts by accepting a layout and a block with the contents" do mock_app do template(:main_outer_layout) { "

Title

\n>{ yield }" } template(:an_inner_layout) { "

Subtitle

\n>{ yield }" } template(:a_page) { "

Contents.

\n" } get('/') do wlang :main_outer_layout, :layout => false do wlang :an_inner_layout do wlang :a_page end end end end get '/' assert ok? assert_body "

Title

\n

Subtitle

\n

Contents.

\n" end end rescue LoadError warn "#{$!.to_s}: skipping wlang tests" end sinatra-1.4.3/test/server_test.rb0000644000004100000410000000164512161612727017074 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) require 'stringio' module Rack::Handler class Mock extend Test::Unit::Assertions def self.run(app, options={}) assert(app < Sinatra::Base) assert_equal 9001, options[:Port] assert_equal 'foo.local', options[:Host] yield new end def stop end end register 'mock', 'Rack::Handler::Mock' end class ServerTest < Test::Unit::TestCase setup do mock_app do set :server, 'mock' set :bind, 'foo.local' set :port, 9001 end $stderr = StringIO.new end def teardown $stderr = STDERR end it "locates the appropriate Rack handler and calls ::run" do @app.run! end it "sets options on the app before running" do @app.run! :sessions => true assert @app.sessions? end it "falls back on the next server handler when not found" do @app.run! :server => %w[foo bar mock] end end sinatra-1.4.3/test/filter_test.rb0000644000004100000410000002554512161612727017060 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) class BeforeFilterTest < Test::Unit::TestCase it "executes filters in the order defined" do count = 0 mock_app do get('/') { 'Hello World' } before do assert_equal 0, count count = 1 end before do assert_equal 1, count count = 2 end end get '/' assert ok? assert_equal 2, count assert_equal 'Hello World', body end it "can modify the request" do mock_app do get('/foo') { 'foo' } get('/bar') { 'bar' } before { request.path_info = '/bar' } end get '/foo' assert ok? assert_equal 'bar', body end it "can modify instance variables available to routes" do mock_app do before { @foo = 'bar' } get('/foo') { @foo } end get '/foo' assert ok? assert_equal 'bar', body end it "allows redirects" do mock_app do before { redirect '/bar' } get('/foo') do fail 'before block should have halted processing' 'ORLY?!' end end get '/foo' assert redirect? assert_equal 'http://example.org/bar', response['Location'] assert_equal '', body end it "does not modify the response with its return value" do mock_app do before { 'Hello World!' } get('/foo') do assert_equal [], response.body 'cool' end end get '/foo' assert ok? assert_equal 'cool', body end it "does modify the response with halt" do mock_app do before { halt 302, 'Hi' } get '/foo' do "should not happen" end end get '/foo' assert_equal 302, response.status assert_equal 'Hi', body end it "gives you access to params" do mock_app do before { @foo = params['foo'] } get('/foo') { @foo } end get '/foo?foo=cool' assert ok? assert_equal 'cool', body end it "properly unescapes parameters" do mock_app do before { @foo = params['foo'] } get('/foo') { @foo } end get '/foo?foo=bar%3Abaz%2Fbend' assert ok? assert_equal 'bar:baz/bend', body end it "runs filters defined in superclasses" do base = Class.new(Sinatra::Base) base.before { @foo = 'hello from superclass' } mock_app(base) { get('/foo') { @foo } } get '/foo' assert_equal 'hello from superclass', body end it 'does not run before filter when serving static files' do ran_filter = false mock_app do before { ran_filter = true } set :static, true set :public_folder, File.dirname(__FILE__) end get "/#{File.basename(__FILE__)}" assert ok? assert_equal File.read(__FILE__), body assert !ran_filter end it 'takes an optional route pattern' do ran_filter = false mock_app do before("/b*") { ran_filter = true } get('/foo') { } get('/bar') { } end get '/foo' assert !ran_filter get '/bar' assert ran_filter end it 'generates block arguments from route pattern' do subpath = nil mock_app do before("/foo/:sub") { |s| subpath = s } get('/foo/*') { } end get '/foo/bar' assert_equal subpath, 'bar' end it 'can catch exceptions in before filters and handle them properly' do doodle = '' mock_app do before do doodle += 'This begins' raise StandardError, "before" end get "/" do doodle = 'and runs' end error 500 do "Error handled #{env['sinatra.error'].message}" end end doodle = '' get '/' assert_equal 'Error handled before', body assert_equal 'This begins', doodle end end class AfterFilterTest < Test::Unit::TestCase it "executes before and after filters in correct order" do invoked = 0 mock_app do before { invoked = 2 } get('/') { invoked += 2; 'hello' } after { invoked *= 2 } end get '/' assert ok? assert_equal 8, invoked end it "executes filters in the order defined" do count = 0 mock_app do get('/') { 'Hello World' } after do assert_equal 0, count count = 1 end after do assert_equal 1, count count = 2 end end get '/' assert ok? assert_equal 2, count assert_equal 'Hello World', body end it "allows redirects" do mock_app do get('/foo') { 'ORLY' } after { redirect '/bar' } end get '/foo' assert redirect? assert_equal 'http://example.org/bar', response['Location'] assert_equal '', body end it "does not modify the response with its return value" do mock_app do get('/foo') { 'cool' } after { 'Hello World!' } end get '/foo' assert ok? assert_equal 'cool', body end it "does modify the response with halt" do mock_app do get '/foo' do "should not be returned" end after { halt 302, 'Hi' } end get '/foo' assert_equal 302, response.status assert_equal 'Hi', body end it "runs filters defined in superclasses" do count = 2 base = Class.new(Sinatra::Base) base.after { count *= 2 } mock_app(base) do get('/foo') do count += 2 "ok" end end get '/foo' assert_equal 8, count end it 'does not run after filter when serving static files' do ran_filter = false mock_app do after { ran_filter = true } set :static, true set :public_folder, File.dirname(__FILE__) end get "/#{File.basename(__FILE__)}" assert ok? assert_equal File.read(__FILE__), body assert !ran_filter end it 'takes an optional route pattern' do ran_filter = false mock_app do after("/b*") { ran_filter = true } get('/foo') { } get('/bar') { } end get '/foo' assert !ran_filter get '/bar' assert ran_filter end it 'changes to path_info from a pattern matching before filter are respected when routing' do mock_app do before('/foo') { request.path_info = '/bar' } get('/bar') { 'blah' } end get '/foo' assert ok? assert_equal 'blah', body end it 'generates block arguments from route pattern' do subpath = nil mock_app do after("/foo/:sub") { |s| subpath = s } get('/foo/*') { } end get '/foo/bar' assert_equal subpath, 'bar' end it 'is possible to access url params from the route param' do ran = false mock_app do get('/foo/*') { } before('/foo/:sub') do assert_equal params[:sub], 'bar' ran = true end end get '/foo/bar' assert ran end it 'is possible to apply host_name conditions to before filters with no path' do ran = false mock_app do before(:host_name => 'example.com') { ran = true } get('/') { 'welcome' } end get('/', {}, { 'HTTP_HOST' => 'example.org' }) assert !ran get('/', {}, { 'HTTP_HOST' => 'example.com' }) assert ran end it 'is possible to apply host_name conditions to before filters with a path' do ran = false mock_app do before('/foo', :host_name => 'example.com') { ran = true } get('/') { 'welcome' } end get('/', {}, { 'HTTP_HOST' => 'example.com' }) assert !ran get('/foo', {}, { 'HTTP_HOST' => 'example.org' }) assert !ran get('/foo', {}, { 'HTTP_HOST' => 'example.com' }) assert ran end it 'is possible to apply host_name conditions to after filters with no path' do ran = false mock_app do after(:host_name => 'example.com') { ran = true } get('/') { 'welcome' } end get('/', {}, { 'HTTP_HOST' => 'example.org' }) assert !ran get('/', {}, { 'HTTP_HOST' => 'example.com' }) assert ran end it 'is possible to apply host_name conditions to after filters with a path' do ran = false mock_app do after('/foo', :host_name => 'example.com') { ran = true } get('/') { 'welcome' } end get('/', {}, { 'HTTP_HOST' => 'example.com' }) assert !ran get('/foo', {}, { 'HTTP_HOST' => 'example.org' }) assert !ran get('/foo', {}, { 'HTTP_HOST' => 'example.com' }) assert ran end it 'is possible to apply user_agent conditions to before filters with no path' do ran = false mock_app do before(:user_agent => /foo/) { ran = true } get('/') { 'welcome' } end get('/', {}, { 'HTTP_USER_AGENT' => 'bar' }) assert !ran get('/', {}, { 'HTTP_USER_AGENT' => 'foo' }) assert ran end it 'is possible to apply user_agent conditions to before filters with a path' do ran = false mock_app do before('/foo', :user_agent => /foo/) { ran = true } get('/') { 'welcome' } end get('/', {}, { 'HTTP_USER_AGENT' => 'foo' }) assert !ran get('/foo', {}, { 'HTTP_USER_AGENT' => 'bar' }) assert !ran get('/foo', {}, { 'HTTP_USER_AGENT' => 'foo' }) assert ran end it 'can add params' do mock_app do before { params['foo'] = 'bar' } get('/') { params['foo'] } end get '/' assert_body 'bar' end it 'can remove params' do mock_app do before { params.delete('foo') } get('/') { params['foo'].to_s } end get '/?foo=bar' assert_body '' end it 'is possible to apply user_agent conditions to after filters with no path' do ran = false mock_app do after(:user_agent => /foo/) { ran = true } get('/') { 'welcome' } end get('/', {}, { 'HTTP_USER_AGENT' => 'bar' }) assert !ran get('/', {}, { 'HTTP_USER_AGENT' => 'foo' }) assert ran end it 'is possible to apply user_agent conditions to after filters with a path' do ran = false mock_app do after('/foo', :user_agent => /foo/) { ran = true } get('/') { 'welcome' } end get('/', {}, { 'HTTP_USER_AGENT' => 'foo' }) assert !ran get('/foo', {}, { 'HTTP_USER_AGENT' => 'bar' }) assert !ran get('/foo', {}, { 'HTTP_USER_AGENT' => 'foo' }) assert ran end it 'only triggeres provides condition if conforms with current Content-Type' do mock_app do before(:provides => :txt) { @type = 'txt' } before(:provides => :html) { @type = 'html' } get('/') { @type } end get('/', {}, { 'HTTP_ACCEPT' => '*/*' }) assert_body 'txt' end it 'can catch exceptions in after filters and handle them properly' do doodle = '' mock_app do after do doodle += ' and after' raise StandardError, "after" end get "/foo" do doodle = 'Been now' raise StandardError, "now" end get "/" do doodle = 'Been now' end error 500 do "Error handled #{env['sinatra.error'].message}" end end get '/foo' assert_equal 'Error handled now', body assert_equal 'Been now and after', doodle doodle = '' get '/' assert_equal 'Error handled after', body assert_equal 'Been now and after', doodle end end sinatra-1.4.3/test/scss_test.rb0000644000004100000410000000461012161612727016534 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) begin raise LoadError, 'sass not supported on Ruby 2.0' if RUBY_VERSION >= '2.0' require 'sass' class ScssTest < Test::Unit::TestCase def scss_app(options = {}, &block) mock_app do set :views, File.dirname(__FILE__) + '/views' set options get('/', &block) end get '/' end it 'renders inline Scss strings' do scss_app { scss "#scss {\n background-color: white; }\n" } assert ok? assert_equal "#scss {\n background-color: white; }\n", body end it 'defaults content type to css' do scss_app { scss "#scss {\n background-color: white; }\n" } assert ok? assert_equal "text/css;charset=utf-8", response['Content-Type'] end it 'defaults allows setting content type per route' do scss_app do content_type :html scss "#scss {\n background-color: white; }\n" end assert ok? assert_equal "text/html;charset=utf-8", response['Content-Type'] end it 'defaults allows setting content type globally' do scss_app(:scss => { :content_type => 'html' }) { scss "#scss {\n background-color: white; }\n" } assert ok? assert_equal "text/html;charset=utf-8", response['Content-Type'] end it 'renders .scss files in views path' do scss_app { scss :hello } assert ok? assert_equal "#scss {\n background-color: white; }\n", body end it 'ignores the layout option' do scss_app { scss :hello, :layout => :layout2 } assert ok? assert_equal "#scss {\n background-color: white; }\n", body end it "raises error if template not found" do mock_app { get('/') { scss(:no_such_template) } } assert_raise(Errno::ENOENT) { get('/') } end it "passes scss options to the scss engine" do scss_app do scss( "#scss {\n background-color: white;\n color: black\n}", :style => :compact ) end assert ok? assert_equal "#scss { background-color: white; color: black; }\n", body end it "passes default scss options to the scss engine" do mock_app do set :scss, {:style => :compact} # default scss style is :nested get('/') { scss("#scss {\n background-color: white;\n color: black;\n}") } end get '/' assert ok? assert_equal "#scss { background-color: white; color: black; }\n", body end end rescue LoadError warn "#{$!.to_s}: skipping scss tests" end sinatra-1.4.3/test/base_test.rb0000644000004100000410000001155412161612727016500 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) class BaseTest < Test::Unit::TestCase def test_default assert true end describe 'Sinatra::Base subclasses' do class TestApp < Sinatra::Base get('/') { 'Hello World' } end it 'include Rack::Utils' do assert TestApp.included_modules.include?(Rack::Utils) end it 'processes requests with #call' do assert TestApp.respond_to?(:call) request = Rack::MockRequest.new(TestApp) response = request.get('/') assert response.ok? assert_equal 'Hello World', response.body end class TestApp < Sinatra::Base get '/state' do @foo ||= "new" body = "Foo: #{@foo}" @foo = 'discard' body end end it 'does not maintain state between requests' do request = Rack::MockRequest.new(TestApp) 2.times do response = request.get('/state') assert response.ok? assert_equal 'Foo: new', response.body end end it "passes the subclass to configure blocks" do ref = nil TestApp.configure { |app| ref = app } assert_equal TestApp, ref end it "allows the configure block arg to be omitted and does not change context" do context = nil TestApp.configure { context = self } assert_equal self, context end end describe "Sinatra::Base#new" do it 'returns a wrapper' do assert_equal Sinatra::Wrapper, Sinatra::Base.new.class end it 'implements a nice inspect' do assert_equal '#', Sinatra::Base.new.inspect end it 'exposes settings' do assert_equal Sinatra::Base.settings, Sinatra::Base.new.settings end it 'exposes helpers' do assert_equal 'image/jpeg', Sinatra::Base.new.helpers.mime_type(:jpg) end end describe "Sinatra::Base as Rack middleware" do app = lambda { |env| headers = {'X-Downstream' => 'true'} headers['X-Route-Missing'] = env['sinatra.route-missing'] || '' [210, headers, ['Hello from downstream']] } class TestMiddleware < Sinatra::Base end it 'creates a middleware that responds to #call with .new' do middleware = TestMiddleware.new(app) assert middleware.respond_to?(:call) end it 'exposes the downstream app' do middleware = TestMiddleware.new!(app) assert_same app, middleware.app end class TestMiddleware < Sinatra::Base def route_missing env['sinatra.route-missing'] = '1' super end get('/') { 'Hello from middleware' } end middleware = TestMiddleware.new(app) request = Rack::MockRequest.new(middleware) it 'intercepts requests' do response = request.get('/') assert response.ok? assert_equal 'Hello from middleware', response.body end it 'automatically forwards requests downstream when no matching route found' do response = request.get('/missing') assert_equal 210, response.status assert_equal 'Hello from downstream', response.body end it 'calls #route_missing before forwarding downstream' do response = request.get('/missing') assert_equal '1', response['X-Route-Missing'] end class TestMiddleware < Sinatra::Base get('/low-level-forward') { app.call(env) } end it 'can call the downstream app directly and return result' do response = request.get('/low-level-forward') assert_equal 210, response.status assert_equal 'true', response['X-Downstream'] assert_equal 'Hello from downstream', response.body end class TestMiddleware < Sinatra::Base get '/explicit-forward' do response['X-Middleware'] = 'true' res = forward assert_nil res assert_equal 210, response.status assert_equal 'true', response['X-Downstream'] assert_equal ['Hello from downstream'], response.body 'Hello after explicit forward' end end it 'forwards the request downstream and integrates the response into the current context' do response = request.get('/explicit-forward') assert_equal 210, response.status assert_equal 'true', response['X-Downstream'] assert_equal 'Hello after explicit forward', response.body assert_equal '28', response['Content-Length'] end app_content_length = lambda {|env| [200, {'Content-Length' => '16'}, 'From downstream!']} class TestMiddlewareContentLength < Sinatra::Base get '/forward' do res = forward 'From after explicit forward!' end end middleware_content_length = TestMiddlewareContentLength.new(app_content_length) request_content_length = Rack::MockRequest.new(middleware_content_length) it "sets content length for last response" do response = request_content_length.get('/forward') assert_equal '28', response['Content-Length'] end end end sinatra-1.4.3/test/erb_test.rb0000644000004100000410000000507312161612727016335 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) class ERBTest < Test::Unit::TestCase def engine Tilt::ERBTemplate end def setup Tilt.prefer engine, :erb super end def erb_app(&block) mock_app do set :views, File.dirname(__FILE__) + '/views' get('/', &block) end get '/' end it 'uses the correct engine' do assert_equal engine, Tilt[:erb] end it 'renders inline ERB strings' do erb_app { erb '<%= 1 + 1 %>' } assert ok? assert_equal '2', body end it 'renders .erb files in views path' do erb_app { erb :hello } assert ok? assert_equal "Hello World\n", body end it 'takes a :locals option' do erb_app do locals = {:foo => 'Bar'} erb '<%= foo %>', :locals => locals end assert ok? assert_equal 'Bar', body end it "renders with inline layouts" do mock_app do layout { 'THIS. IS. <%= yield.upcase %>!' } get('/') { erb 'Sparta' } end get '/' assert ok? assert_equal 'THIS. IS. SPARTA!', body end it "renders with file layouts" do erb_app { erb 'Hello World', :layout => :layout2 } assert ok? assert_body "ERB Layout!\nHello World" end it "renders erb with blocks" do mock_app do def container @_out_buf << "THIS." yield @_out_buf << "SPARTA!" end def is; "IS." end get('/') { erb '<% container do %> <%= is %> <% end %>' } end get '/' assert ok? assert_equal 'THIS. IS. SPARTA!', body end it "can be used in a nested fashion for partials and whatnot" do mock_app do template(:inner) { "<%= 'hi' %>" } template(:outer) { "<%= erb :inner %>" } get('/') { erb :outer } end get '/' assert ok? assert_equal 'hi', body end it "can render truly nested layouts by accepting a layout and a block with the contents" do mock_app do template(:main_outer_layout) { "

Title

\n<%= yield %>" } template(:an_inner_layout) { "

Subtitle

\n<%= yield %>" } template(:a_page) { "

Contents.

\n" } get('/') do erb :main_outer_layout, :layout => false do erb :an_inner_layout do erb :a_page end end end end get '/' assert ok? assert_body "

Title

\n

Subtitle

\n

Contents.

\n" end end begin require 'erubis' class ErubisTest < ERBTest def engine; Tilt::ErubisTemplate end end rescue LoadError warn "#{$!.to_s}: skipping erubis tests" end sinatra-1.4.3/test/helpers_test.rb0000644000004100000410000013634312161612727017234 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) require 'date' require 'json' class HelpersTest < Test::Unit::TestCase def test_default assert true end def status_app(code, &block) code += 2 if [204, 205, 304].include? code block ||= proc { } mock_app do get('/') do status code instance_eval(&block).inspect end end get '/' end describe 'status' do it 'sets the response status code' do status_app 207 assert_equal 207, response.status end end describe 'not_found?' do it 'is true for status == 404' do status_app(404) { not_found? } assert_body 'true' end it 'is false for status gt 404' do status_app(405) { not_found? } assert_body 'false' end it 'is false for status lt 404' do status_app(403) { not_found? } assert_body 'false' end end describe 'informational?' do it 'is true for 1xx status' do status_app(100 + rand(100)) { informational? } assert_body 'true' end it 'is false for status > 199' do status_app(200 + rand(400)) { informational? } assert_body 'false' end end describe 'success?' do it 'is true for 2xx status' do status_app(200 + rand(100)) { success? } assert_body 'true' end it 'is false for status < 200' do status_app(100 + rand(100)) { success? } assert_body 'false' end it 'is false for status > 299' do status_app(300 + rand(300)) { success? } assert_body 'false' end end describe 'redirect?' do it 'is true for 3xx status' do status_app(300 + rand(100)) { redirect? } assert_body 'true' end it 'is false for status < 300' do status_app(200 + rand(100)) { redirect? } assert_body 'false' end it 'is false for status > 399' do status_app(400 + rand(200)) { redirect? } assert_body 'false' end end describe 'client_error?' do it 'is true for 4xx status' do status_app(400 + rand(100)) { client_error? } assert_body 'true' end it 'is false for status < 400' do status_app(200 + rand(200)) { client_error? } assert_body 'false' end it 'is false for status > 499' do status_app(500 + rand(100)) { client_error? } assert_body 'false' end end describe 'server_error?' do it 'is true for 5xx status' do status_app(500 + rand(100)) { server_error? } assert_body 'true' end it 'is false for status < 500' do status_app(200 + rand(300)) { server_error? } assert_body 'false' end end describe 'body' do it 'takes a block for deferred body generation' do mock_app do get('/') { body { 'Hello World' } } end get '/' assert_equal 'Hello World', body end it 'takes a String, Array, or other object responding to #each' do mock_app { get('/') { body 'Hello World' } } get '/' assert_equal 'Hello World', body end it 'can be used with other objects' do mock_app do get '/' do body :hello => 'from json' end after do if Hash === response.body body response.body[:hello] end end end get '/' assert_body 'from json' end it 'can be set in after filter' do mock_app do get('/') { body 'route' } after { body 'filter' } end get '/' assert_body 'filter' end end describe 'redirect' do it 'uses a 302 when only a path is given' do mock_app do get('/') do redirect '/foo' fail 'redirect should halt' end end get '/' assert_equal 302, status assert_equal '', body assert_equal 'http://example.org/foo', response['Location'] end it 'uses the code given when specified' do mock_app do get('/') do redirect '/foo', 301 fail 'redirect should halt' end end get '/' assert_equal 301, status assert_equal '', body assert_equal 'http://example.org/foo', response['Location'] end it 'redirects back to request.referer when passed back' do mock_app { get('/try_redirect') { redirect back } } request = Rack::MockRequest.new(@app) response = request.get('/try_redirect', 'HTTP_REFERER' => '/foo') assert_equal 302, response.status assert_equal 'http://example.org/foo', response['Location'] end it 'redirects using a non-standard HTTP port' do mock_app { get('/') { redirect '/foo' } } request = Rack::MockRequest.new(@app) response = request.get('/', 'SERVER_PORT' => '81') assert_equal 'http://example.org:81/foo', response['Location'] end it 'redirects using a non-standard HTTPS port' do mock_app { get('/') { redirect '/foo' } } request = Rack::MockRequest.new(@app) response = request.get('/', 'SERVER_PORT' => '444') 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 { get('/') { redirect '/foo' } } request = Rack::MockRequest.new(@app) response = request.get('/', 'HTTP_X_FORWARDED_HOST' => 'example.com', 'SERVER_PORT' => '8080') assert_equal 'http://example.com/foo', response['Location'] end it 'accepts absolute URIs' do mock_app do get('/') do redirect 'http://google.com' fail 'redirect should halt' end end get '/' assert_equal 302, status assert_equal '', body assert_equal 'http://google.com', response['Location'] end it 'accepts absolute URIs with a different schema' do mock_app do get('/') do redirect 'mailto:jsmith@example.com' fail 'redirect should halt' end end get '/' assert_equal 302, status assert_equal '', body assert_equal 'mailto:jsmith@example.com', response['Location'] end it 'accepts a URI object instead of a String' do mock_app do get('/') { redirect URI.parse('http://sinatrarb.com') } end get '/' assert_equal 302, status assert_equal '', body assert_equal 'http://sinatrarb.com', response['Location'] end end describe 'error' do it 'sets a status code and halts' do mock_app do get('/') do error 501 fail 'error should halt' end end get '/' assert_equal 501, status assert_equal '', body end it 'takes an optional body' do mock_app do get('/') do error 501, 'FAIL' fail 'error should halt' end end get '/' assert_equal 501, status assert_equal 'FAIL', body end it 'should not invoke error handler when setting status inside an error handler' do mock_app do disable :raise_errors not_found do body "not_found handler" status 404 end error do body "error handler" status 404 end get '/' do raise end end get '/' assert_equal 404, status assert_equal 'error handler', body end it 'should not reset the content-type to html for error handlers' do mock_app do disable :raise_errors before { content_type "application/json;charset=utf-8" } not_found { JSON.dump("error" => "Not Found") } end get '/' assert_equal 404, status assert_equal 'application/json;charset=utf-8', response.content_type end it 'should not invoke error handler when halting with 500 inside an error handler' do mock_app do disable :raise_errors not_found do body "not_found handler" halt 404 end error do body "error handler" halt 404 end get '/' do raise end end get '/' assert_equal 404, status assert_equal 'error handler', body end it 'should not invoke not_found handler when halting with 404 inside a not found handler' do mock_app do disable :raise_errors not_found do body "not_found handler" halt 500 end error do body "error handler" halt 500 end end get '/' assert_equal 500, status assert_equal 'not_found handler', body end it 'uses a 500 status code when first argument is a body' do mock_app do get('/') do error 'FAIL' fail 'error should halt' end end get '/' assert_equal 500, status assert_equal 'FAIL', body end end describe 'not_found' do it 'halts with a 404 status' do mock_app do get('/') do not_found fail 'not_found should halt' end end get '/' assert_equal 404, status assert_equal '', body end it 'does not set a X-Cascade header' do mock_app do get('/') do not_found fail 'not_found should halt' end end get '/' assert_equal 404, status assert_equal nil, response.headers['X-Cascade'] end end describe 'headers' do it 'sets headers on the response object when given a Hash' do mock_app do get('/') do headers 'X-Foo' => 'bar', 'X-Baz' => 'bling' 'kthx' end end get '/' assert ok? assert_equal 'bar', response['X-Foo'] assert_equal 'bling', response['X-Baz'] assert_equal 'kthx', body end it 'returns the response headers hash when no hash provided' do mock_app do get('/') do headers['X-Foo'] = 'bar' 'kthx' end end get '/' assert ok? assert_equal 'bar', response['X-Foo'] end end describe 'session' do it 'uses the existing rack.session' do mock_app do get('/') do session[:foo] end end get('/', {}, { 'rack.session' => { :foo => 'bar' } }) assert_equal 'bar', body end it 'creates a new session when none provided' do mock_app do enable :sessions get('/') do assert session[:foo].nil? session[:foo] = 'bar' redirect '/hi' end get('/hi') do "hi #{session[:foo]}" end end get '/' follow_redirect! assert_equal 'hi bar', body end it 'inserts session middleware' do mock_app do enable :sessions get('/') do assert env['rack.session'] assert env['rack.session.options'] 'ok' end end get '/' assert_body 'ok' end it 'sets a default session secret' do mock_app do enable :sessions get('/') do secret = env['rack.session.options'][:secret] assert secret assert_equal secret, settings.session_secret 'ok' end end get '/' assert_body 'ok' end it 'allows disabling session secret' do mock_app do enable :sessions disable :session_secret get('/') do assert !env['rack.session.options'].include?(:session_secret) 'ok' end end # Silence warnings since Rack::Session::Cookie complains about the non-present session secret silence_warnings do get '/' end assert_body 'ok' end it 'accepts an options hash' do mock_app do set :sessions, :foo => :bar get('/') do assert_equal env['rack.session.options'][:foo], :bar 'ok' end end get '/' assert_body 'ok' end end describe 'mime_type' do include Sinatra::Helpers it "looks up mime types in Rack's MIME registry" do Rack::Mime::MIME_TYPES['.foo'] = 'application/foo' assert_equal 'application/foo', mime_type('foo') assert_equal 'application/foo', mime_type('.foo') assert_equal 'application/foo', mime_type(:foo) end it 'returns nil when given nil' do assert mime_type(nil).nil? end it 'returns nil when media type not registered' do assert mime_type(:bizzle).nil? end it 'returns the argument when given a media type string' do assert_equal 'text/plain', mime_type('text/plain') end it 'turns AcceptEntry into String' do type = mime_type(Sinatra::Request::AcceptEntry.new('text/plain')) assert_equal String, type.class assert_equal 'text/plain', type end end test 'Base.mime_type registers mime type' do mock_app do mime_type :foo, 'application/foo' get('/') do "foo is #{mime_type(:foo)}" end end get '/' assert_equal 'foo is application/foo', body end describe 'content_type' do it 'sets the Content-Type header' do mock_app do get('/') do content_type 'text/plain' 'Hello World' end end get '/' assert_equal 'text/plain;charset=utf-8', response['Content-Type'] assert_equal 'Hello World', body end it 'takes media type parameters (like charset=)' do mock_app do get('/') do content_type 'text/html', :charset => 'latin1' "

Hello, World

" end end get '/' assert ok? assert_equal 'text/html;charset=latin1', response['Content-Type'] assert_equal "

Hello, World

", body end it "looks up symbols in Rack's mime types dictionary" do Rack::Mime::MIME_TYPES['.foo'] = 'application/foo' mock_app do get('/foo.xml') do content_type :foo "I AM FOO" end end get '/foo.xml' assert ok? assert_equal 'application/foo', response['Content-Type'] assert_equal 'I AM FOO', body end it 'fails when no mime type is registered for the argument provided' do mock_app do get('/foo.xml') do content_type :bizzle "I AM FOO" end end assert_raise(RuntimeError) { get '/foo.xml' } end it 'only sets default charset for specific mime types' do tests_ran = false mock_app do mime_type :foo, 'text/foo' mime_type :bar, 'application/bar' mime_type :baz, 'application/baz' add_charset << mime_type(:baz) get('/') do assert_equal content_type(:txt), 'text/plain;charset=utf-8' assert_equal content_type(:css), 'text/css;charset=utf-8' assert_equal content_type(:html), 'text/html;charset=utf-8' assert_equal content_type(:foo), 'text/foo;charset=utf-8' assert_equal content_type(:xml), 'application/xml;charset=utf-8' assert_equal content_type(:xhtml), 'application/xhtml+xml;charset=utf-8' assert_equal content_type(:js), 'application/javascript;charset=utf-8' assert_equal content_type(:json), 'application/json;charset=utf-8' assert_equal content_type(:bar), 'application/bar' assert_equal content_type(:png), 'image/png' assert_equal content_type(:baz), 'application/baz;charset=utf-8' tests_ran = true "done" end end get '/' assert tests_ran end it 'handles already present params' do mock_app do get('/') do content_type 'foo/bar;level=1', :charset => 'utf-8' 'ok' end end get '/' assert_equal 'foo/bar;level=1, charset=utf-8', response['Content-Type'] end it 'does not add charset if present' do mock_app do get('/') do content_type 'text/plain;charset=utf-16' 'ok' end end get '/' assert_equal 'text/plain;charset=utf-16', response['Content-Type'] end it 'properly encodes parameters with delimiter characters' do mock_app do before '/comma' do content_type 'image/png', :comment => 'Hello, world!' end before '/semicolon' do content_type 'image/png', :comment => 'semi;colon' end before '/quote' do content_type 'image/png', :comment => '"Whatever."' end get('*') { 'ok' } end get '/comma' assert_equal 'image/png;comment="Hello, world!"', response['Content-Type'] get '/semicolon' assert_equal 'image/png;comment="semi;colon"', response['Content-Type'] get '/quote' assert_equal 'image/png;comment="\"Whatever.\""', response['Content-Type'] end end describe 'attachment' do def attachment_app(filename=nil) mock_app do get('/attachment') do attachment filename response.write("") end end end it 'sets the Content-Type response header' do attachment_app('test.xml') get '/attachment' assert_equal 'application/xml;charset=utf-8', response['Content-Type'] assert_equal '', body end it 'sets the Content-Type response header without extname' do attachment_app('test') get '/attachment' assert_equal 'text/html;charset=utf-8', response['Content-Type'] assert_equal '', body end it 'sets the Content-Type response header with extname' do mock_app do get('/attachment') do content_type :atom attachment 'test.xml' response.write("") end end get '/attachment' assert_equal 'application/atom+xml', response['Content-Type'] assert_equal '', body end end describe 'send_file' do setup do @file = File.dirname(__FILE__) + '/file.txt' File.open(@file, 'wb') { |io| io.write('Hello World') } end def teardown File.unlink @file @file = nil end def send_file_app(opts={}) path = @file mock_app { get '/file.txt' do send_file path, opts end } end it "sends the contents of the file" do send_file_app get '/file.txt' assert ok? assert_equal 'Hello World', body end it 'sets the Content-Type response header if a mime-type can be located' do send_file_app get '/file.txt' assert_equal 'text/plain;charset=utf-8', response['Content-Type'] end it 'sets the Content-Type response header if type option is set to a file extension' do send_file_app :type => 'html' get '/file.txt' assert_equal 'text/html;charset=utf-8', response['Content-Type'] end it 'sets the Content-Type response header if type option is set to a mime type' do send_file_app :type => 'application/octet-stream' get '/file.txt' assert_equal 'application/octet-stream', response['Content-Type'] end it 'sets the Content-Length response header' do send_file_app get '/file.txt' assert_equal 'Hello World'.length.to_s, response['Content-Length'] end it 'sets the Last-Modified response header' do send_file_app get '/file.txt' assert_equal File.mtime(@file).httpdate, response['Last-Modified'] end it 'allows passing in a different Last-Modified response header with :last_modified' do time = Time.now send_file_app :last_modified => time get '/file.txt' assert_equal time.httpdate, response['Last-Modified'] end it "returns a 404 when not found" do mock_app { get('/') { send_file 'this-file-does-not-exist.txt' } } get '/' assert not_found? end it "does not set the Content-Disposition header by default" do send_file_app get '/file.txt' assert_nil response['Content-Disposition'] end it "sets the Content-Disposition header when :disposition set to 'attachment'" do send_file_app :disposition => 'attachment' get '/file.txt' assert_equal 'attachment; filename="file.txt"', response['Content-Disposition'] end it "does not set add a file name if filename is false" do send_file_app :disposition => 'inline', :filename => false get '/file.txt' assert_equal 'inline', response['Content-Disposition'] end it "sets the Content-Disposition header when :disposition set to 'inline'" do send_file_app :disposition => 'inline' get '/file.txt' assert_equal 'inline; filename="file.txt"', response['Content-Disposition'] end it "sets the Content-Disposition header when :filename provided" do send_file_app :filename => 'foo.txt' get '/file.txt' assert_equal 'attachment; filename="foo.txt"', response['Content-Disposition'] end it 'allows setting a custom status code' do send_file_app :status => 201 get '/file.txt' assert_status 201 end it "is able to send files with unknown mime type" do @file = File.dirname(__FILE__) + '/file.foobar' File.open(@file, 'wb') { |io| io.write('Hello World') } send_file_app get '/file.txt' assert_equal 'application/octet-stream', response['Content-Type'] end it "does not override Content-Type if already set and no explicit type is given" do path = @file mock_app do get('/') do content_type :png send_file path end end get '/' assert_equal 'image/png', response['Content-Type'] end it "does override Content-Type even if already set, if explicit type is given" do path = @file mock_app do get('/') do content_type :png send_file path, :type => :gif end end get '/' assert_equal 'image/gif', response['Content-Type'] end end describe 'cache_control' do setup do mock_app do get('/foo') do cache_control :public, :no_cache, :max_age => 60.0 'Hello World' end get('/bar') do cache_control :public, :no_cache 'Hello World' end end end it 'sets the Cache-Control header' do get '/foo' assert_equal ['public', 'no-cache', 'max-age=60'], response['Cache-Control'].split(', ') end it 'last argument does not have to be a hash' do get '/bar' assert_equal ['public', 'no-cache'], response['Cache-Control'].split(', ') end end describe 'expires' do setup do mock_app do get('/foo') do expires 60, :public, :no_cache 'Hello World' end get('/bar') { expires Time.now } get('/baz') { expires Time.at(0) } get('/blah') do obj = Object.new def obj.method_missing(*a, &b) 60.send(*a, &b) end def obj.is_a?(thing) 60.is_a?(thing) end expires obj, :public, :no_cache 'Hello World' end get('/boom') { expires '9999' } end end it 'sets the Cache-Control header' do get '/foo' assert_equal ['public', 'no-cache', 'max-age=60'], response['Cache-Control'].split(', ') end it 'sets the Expires header' do get '/foo' assert_not_nil response['Expires'] end it 'allows passing Time.now objects' do get '/bar' assert_not_nil response['Expires'] end it 'allows passing Time.at objects' do get '/baz' assert_equal 'Thu, 01 Jan 1970 00:00:00 GMT', response['Expires'] end it 'accepts values pretending to be a Numeric (like ActiveSupport::Duration)' do get '/blah' assert_equal ['public', 'no-cache', 'max-age=60'], response['Cache-Control'].split(', ') end it 'fails when Time.parse raises an ArgumentError' do assert_raise(ArgumentError) { get '/boom' } end end describe 'last_modified' do it 'ignores nil' do mock_app { get('/') { last_modified nil; 200; } } get '/' assert ! response['Last-Modified'] end it 'does not change a status other than 200' do mock_app do get('/') do status 299 last_modified Time.at(0) 'ok' end end get('/', {}, 'HTTP_IF_MODIFIED_SINCE' => 'Sun, 26 Sep 2030 23:43:52 GMT') assert_status 299 assert_body 'ok' end [Time.now, DateTime.now, Date.today, Time.now.to_i, Struct.new(:to_time).new(Time.now) ].each do |last_modified_time| describe "with #{last_modified_time.class.name}" do setup do mock_app do get('/') do last_modified last_modified_time 'Boo!' end end wrapper = Object.new.extend Sinatra::Helpers @last_modified_time = wrapper.time_for last_modified_time end # fixes strange missing test error when running complete test suite. it("does not complain about missing tests") { } context "when there's no If-Modified-Since header" do it 'sets the Last-Modified header to a valid RFC 2616 date value' do get '/' assert_equal @last_modified_time.httpdate, response['Last-Modified'] end it 'conditional GET misses and returns a body' do get '/' assert_equal 200, status assert_equal 'Boo!', body end end context "when there's an invalid If-Modified-Since header" do it 'sets the Last-Modified header to a valid RFC 2616 date value' do get('/', {}, { 'HTTP_IF_MODIFIED_SINCE' => 'a really weird date' }) assert_equal @last_modified_time.httpdate, response['Last-Modified'] end it 'conditional GET misses and returns a body' do get('/', {}, { 'HTTP_IF_MODIFIED_SINCE' => 'a really weird date' }) assert_equal 200, status assert_equal 'Boo!', body end end context "when the resource has been modified since the If-Modified-Since header date" do it 'sets the Last-Modified header to a valid RFC 2616 date value' do get('/', {}, { 'HTTP_IF_MODIFIED_SINCE' => (@last_modified_time - 1).httpdate }) assert_equal @last_modified_time.httpdate, response['Last-Modified'] end it 'conditional GET misses and returns a body' do get('/', {}, { 'HTTP_IF_MODIFIED_SINCE' => (@last_modified_time - 1).httpdate }) assert_equal 200, status assert_equal 'Boo!', body end it 'does not rely on string comparison' do mock_app do get('/compare') do last_modified "Mon, 18 Oct 2010 20:57:11 GMT" "foo" end end get('/compare', {}, { 'HTTP_IF_MODIFIED_SINCE' => 'Sun, 26 Sep 2010 23:43:52 GMT' }) assert_equal 200, status assert_equal 'foo', body get('/compare', {}, { 'HTTP_IF_MODIFIED_SINCE' => 'Sun, 26 Sep 2030 23:43:52 GMT' }) assert_equal 304, status assert_equal '', body end end context "when the resource has been modified on the exact If-Modified-Since header date" do it 'sets the Last-Modified header to a valid RFC 2616 date value' do get('/', {}, { 'HTTP_IF_MODIFIED_SINCE' => @last_modified_time.httpdate }) assert_equal @last_modified_time.httpdate, response['Last-Modified'] end it 'conditional GET matches and halts' do get( '/', {}, { 'HTTP_IF_MODIFIED_SINCE' => @last_modified_time.httpdate }) assert_equal 304, status assert_equal '', body end end context "when the resource hasn't been modified since the If-Modified-Since header date" do it 'sets the Last-Modified header to a valid RFC 2616 date value' do get('/', {}, { 'HTTP_IF_MODIFIED_SINCE' => (@last_modified_time + 1).httpdate }) assert_equal @last_modified_time.httpdate, response['Last-Modified'] end it 'conditional GET matches and halts' do get('/', {}, { 'HTTP_IF_MODIFIED_SINCE' => (@last_modified_time + 1).httpdate }) assert_equal 304, status assert_equal '', body end end context "If-Unmodified-Since" do it 'results in 200 if resource has not been modified' do get('/', {}, { 'HTTP_IF_UNMODIFIED_SINCE' => 'Sun, 26 Sep 2030 23:43:52 GMT' }) assert_equal 200, status assert_equal 'Boo!', body end it 'results in 412 if resource has been modified' do get('/', {}, { 'HTTP_IF_UNMODIFIED_SINCE' => Time.at(0).httpdate }) assert_equal 412, status assert_equal '', body end end end end end describe 'etag' do context "safe requests" do it 'returns 200 for normal requests' do mock_app do get('/') do etag 'foo' 'ok' end end get '/' assert_status 200 assert_body 'ok' end context "If-None-Match" do it 'returns 304 when If-None-Match is *' do mock_app do get('/') do etag 'foo' 'ok' end end get('/', {}, 'HTTP_IF_NONE_MATCH' => '*') assert_status 304 assert_body '' end it 'returns 200 when If-None-Match is * for new resources' do mock_app do get('/') do etag 'foo', :new_resource => true 'ok' end end get('/', {}, 'HTTP_IF_NONE_MATCH' => '*') assert_status 200 assert_body 'ok' end it 'returns 304 when If-None-Match is * for existing resources' do mock_app do get('/') do etag 'foo', :new_resource => false 'ok' end end get('/', {}, 'HTTP_IF_NONE_MATCH' => '*') assert_status 304 assert_body '' end it 'returns 304 when If-None-Match is the etag' do mock_app do get('/') do etag 'foo' 'ok' end end get('/', {}, 'HTTP_IF_NONE_MATCH' => '"foo"') assert_status 304 assert_body '' end it 'returns 304 when If-None-Match includes the etag' do mock_app do get('/') do etag 'foo' 'ok' end end get('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar", "foo"') assert_status 304 assert_body '' end it 'returns 200 when If-None-Match does not include the etag' do mock_app do get('/') do etag 'foo' 'ok' end end get('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar"') assert_status 200 assert_body 'ok' end it 'ignores If-Modified-Since if If-None-Match does not match' do mock_app do get('/') do etag 'foo' last_modified Time.at(0) 'ok' end end get('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar"') assert_status 200 assert_body 'ok' end it 'does not change a status code other than 2xx or 304' do mock_app do get('/') do status 499 etag 'foo' 'ok' end end get('/', {}, 'HTTP_IF_NONE_MATCH' => '"foo"') assert_status 499 assert_body 'ok' end it 'does change 2xx status codes' do mock_app do get('/') do status 299 etag 'foo' 'ok' end end get('/', {}, 'HTTP_IF_NONE_MATCH' => '"foo"') assert_status 304 assert_body '' end it 'does not send a body on 304 status codes' do mock_app do get('/') do status 304 etag 'foo' 'ok' end end get('/', {}, 'HTTP_IF_NONE_MATCH' => '"foo"') assert_status 304 assert_body '' end end context "If-Match" do it 'returns 200 when If-Match is the etag' do mock_app do get('/') do etag 'foo' 'ok' end end get('/', {}, 'HTTP_IF_MATCH' => '"foo"') assert_status 200 assert_body 'ok' end it 'returns 200 when If-Match includes the etag' do mock_app do get('/') do etag 'foo' 'ok' end end get('/', {}, 'HTTP_IF_MATCH' => '"foo", "bar"') assert_status 200 assert_body 'ok' end it 'returns 200 when If-Match is *' do mock_app do get('/') do etag 'foo' 'ok' end end get('/', {}, 'HTTP_IF_MATCH' => '*') assert_status 200 assert_body 'ok' end it 'returns 412 when If-Match is * for new resources' do mock_app do get('/') do etag 'foo', :new_resource => true 'ok' end end get('/', {}, 'HTTP_IF_MATCH' => '*') assert_status 412 assert_body '' end it 'returns 200 when If-Match is * for existing resources' do mock_app do get('/') do etag 'foo', :new_resource => false 'ok' end end get('/', {}, 'HTTP_IF_MATCH' => '*') assert_status 200 assert_body 'ok' end it 'returns 412 when If-Match does not include the etag' do mock_app do get('/') do etag 'foo' 'ok' end end get('/', {}, 'HTTP_IF_MATCH' => '"bar"') assert_status 412 assert_body '' end end end context "idempotent requests" do it 'returns 200 for normal requests' do mock_app do put('/') do etag 'foo' 'ok' end end put '/' assert_status 200 assert_body 'ok' end context "If-None-Match" do it 'returns 412 when If-None-Match is *' do mock_app do put('/') do etag 'foo' 'ok' end end put('/', {}, 'HTTP_IF_NONE_MATCH' => '*') assert_status 412 assert_body '' end it 'returns 200 when If-None-Match is * for new resources' do mock_app do put('/') do etag 'foo', :new_resource => true 'ok' end end put('/', {}, 'HTTP_IF_NONE_MATCH' => '*') assert_status 200 assert_body 'ok' end it 'returns 412 when If-None-Match is * for existing resources' do mock_app do put('/') do etag 'foo', :new_resource => false 'ok' end end put('/', {}, 'HTTP_IF_NONE_MATCH' => '*') assert_status 412 assert_body '' end it 'returns 412 when If-None-Match is the etag' do mock_app do put '/' do etag 'foo' 'ok' end end put('/', {}, 'HTTP_IF_NONE_MATCH' => '"foo"') assert_status 412 assert_body '' end it 'returns 412 when If-None-Match includes the etag' do mock_app do put('/') do etag 'foo' 'ok' end end put('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar", "foo"') assert_status 412 assert_body '' end it 'returns 200 when If-None-Match does not include the etag' do mock_app do put('/') do etag 'foo' 'ok' end end put('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar"') assert_status 200 assert_body 'ok' end it 'ignores If-Modified-Since if If-None-Match does not match' do mock_app do put('/') do etag 'foo' last_modified Time.at(0) 'ok' end end put('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar"') assert_status 200 assert_body 'ok' end end context "If-Match" do it 'returns 200 when If-Match is the etag' do mock_app do put('/') do etag 'foo' 'ok' end end put('/', {}, 'HTTP_IF_MATCH' => '"foo"') assert_status 200 assert_body 'ok' end it 'returns 200 when If-Match includes the etag' do mock_app do put('/') do etag 'foo' 'ok' end end put('/', {}, 'HTTP_IF_MATCH' => '"foo", "bar"') assert_status 200 assert_body 'ok' end it 'returns 200 when If-Match is *' do mock_app do put('/') do etag 'foo' 'ok' end end put('/', {}, 'HTTP_IF_MATCH' => '*') assert_status 200 assert_body 'ok' end it 'returns 412 when If-Match is * for new resources' do mock_app do put('/') do etag 'foo', :new_resource => true 'ok' end end put('/', {}, 'HTTP_IF_MATCH' => '*') assert_status 412 assert_body '' end it 'returns 200 when If-Match is * for existing resources' do mock_app do put('/') do etag 'foo', :new_resource => false 'ok' end end put('/', {}, 'HTTP_IF_MATCH' => '*') assert_status 200 assert_body 'ok' end it 'returns 412 when If-Match does not include the etag' do mock_app do put('/') do etag 'foo' 'ok' end end put('/', {}, 'HTTP_IF_MATCH' => '"bar"') assert_status 412 assert_body '' end end end context "post requests" do it 'returns 200 for normal requests' do mock_app do post('/') do etag 'foo' 'ok' end end post('/') assert_status 200 assert_body 'ok' end context "If-None-Match" do it 'returns 200 when If-None-Match is *' do mock_app do post('/') do etag 'foo' 'ok' end end post('/', {}, 'HTTP_IF_NONE_MATCH' => '*') assert_status 200 assert_body 'ok' end it 'returns 200 when If-None-Match is * for new resources' do mock_app do post('/') do etag 'foo', :new_resource => true 'ok' end end post('/', {}, 'HTTP_IF_NONE_MATCH' => '*') assert_status 200 assert_body 'ok' end it 'returns 412 when If-None-Match is * for existing resources' do mock_app do post('/') do etag 'foo', :new_resource => false 'ok' end end post('/', {}, 'HTTP_IF_NONE_MATCH' => '*') assert_status 412 assert_body '' end it 'returns 412 when If-None-Match is the etag' do mock_app do post('/') do etag 'foo' 'ok' end end post('/', {}, 'HTTP_IF_NONE_MATCH' => '"foo"') assert_status 412 assert_body '' end it 'returns 412 when If-None-Match includes the etag' do mock_app do post('/') do etag 'foo' 'ok' end end post('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar", "foo"') assert_status 412 assert_body '' end it 'returns 200 when If-None-Match does not include the etag' do mock_app do post('/') do etag 'foo' 'ok' end end post('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar"') assert_status 200 assert_body 'ok' end it 'ignores If-Modified-Since if If-None-Match does not match' do mock_app do post('/') do etag 'foo' last_modified Time.at(0) 'ok' end end post('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar"') assert_status 200 assert_body 'ok' end end context "If-Match" do it 'returns 200 when If-Match is the etag' do mock_app do post('/') do etag 'foo' 'ok' end end post('/', {}, 'HTTP_IF_MATCH' => '"foo"') assert_status 200 assert_body 'ok' end it 'returns 200 when If-Match includes the etag' do mock_app do post('/') do etag 'foo' 'ok' end end post('/', {}, 'HTTP_IF_MATCH' => '"foo", "bar"') assert_status 200 assert_body 'ok' end it 'returns 412 when If-Match is *' do mock_app do post('/') do etag 'foo' 'ok' end end post('/', {}, 'HTTP_IF_MATCH' => '*') assert_status 412 assert_body '' end it 'returns 412 when If-Match is * for new resources' do mock_app do post('/') do etag 'foo', :new_resource => true 'ok' end end post('/', {}, 'HTTP_IF_MATCH' => '*') assert_status 412 assert_body '' end it 'returns 200 when If-Match is * for existing resources' do mock_app do post('/') do etag 'foo', :new_resource => false 'ok' end end post('/', {}, 'HTTP_IF_MATCH' => '*') assert_status 200 assert_body 'ok' end it 'returns 412 when If-Match does not include the etag' do mock_app do post('/') do etag 'foo' 'ok' end end post('/', {}, 'HTTP_IF_MATCH' => '"bar"') assert_status 412 assert_body '' end end end it 'uses a weak etag with the :weak option' do mock_app do get('/') do etag 'FOO', :weak "that's weak, dude." end end get '/' assert_equal 'W/"FOO"', response['ETag'] end it 'raises an ArgumentError for an invalid strength' do mock_app do get('/') do etag 'FOO', :w00t "that's weak, dude." end end assert_raise(ArgumentError) { get('/') } end end describe 'back' do it "makes redirecting back pretty" do mock_app { get('/foo') { redirect back } } get('/foo', {}, 'HTTP_REFERER' => 'http://github.com') assert redirect? assert_equal "http://github.com", response.location end end describe 'uri' do it 'generates absolute urls' do mock_app { get('/') { uri }} get '/' assert_equal 'http://example.org/', body end it 'includes path_info' do mock_app { get('/:name') { uri }} get '/foo' assert_equal 'http://example.org/foo', body end it 'allows passing an alternative to path_info' do mock_app { get('/:name') { uri '/bar' }} get '/foo' assert_equal 'http://example.org/bar', body end it 'includes script_name' do mock_app { get('/:name') { uri '/bar' }} get '/foo', {}, { "SCRIPT_NAME" => '/foo' } assert_equal 'http://example.org/foo/bar', body end it 'handles absolute URIs' do mock_app { get('/') { uri 'http://google.com' }} get '/' assert_equal 'http://google.com', body end it 'handles different protocols' do mock_app { get('/') { uri 'mailto:jsmith@example.com' }} get '/' assert_equal 'mailto:jsmith@example.com', body end it 'is aliased to #url' do mock_app { get('/') { url }} get '/' assert_equal 'http://example.org/', body end it 'is aliased to #to' do mock_app { get('/') { to }} get '/' assert_equal 'http://example.org/', body end end describe 'logger' do it 'logging works when logging is enabled' do mock_app do enable :logging get('/') do logger.info "Program started" logger.warn "Nothing to do!" end end io = StringIO.new get '/', {}, 'rack.errors' => io assert io.string.include?("INFO -- : Program started") assert io.string.include?("WARN -- : Nothing to do") end it 'logging works when logging is disable, but no output is produced' do mock_app do disable :logging get('/') do logger.info "Program started" logger.warn "Nothing to do!" end end io = StringIO.new get '/', {}, 'rack.errors' => io assert !io.string.include?("INFO -- : Program started") assert !io.string.include?("WARN -- : Nothing to do") end it 'does not create a logger when logging is set to nil' do mock_app do set :logging, nil get('/') { logger.inspect } end get '/' assert_body 'nil' end end module ::HelperOne; def one; '1'; end; end module ::HelperTwo; def two; '2'; end; end describe 'Adding new helpers' do it 'takes a list of modules to mix into the app' do mock_app do helpers ::HelperOne, ::HelperTwo get('/one') { one } get('/two') { two } end get '/one' assert_equal '1', body get '/two' assert_equal '2', body end it 'takes a block to mix into the app' do mock_app do helpers do def foo 'foo' end end get('/') { foo } end get '/' assert_equal 'foo', body end it 'evaluates the block in class context so that methods can be aliased' do mock_app do helpers { alias_method :h, :escape_html } get('/') { h('42 < 43') } end get '/' assert ok? assert_equal '42 < 43', body end end end sinatra-1.4.3/test/creole_test.rb0000644000004100000410000000274512161612727017041 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) begin require 'creole' class CreoleTest < Test::Unit::TestCase def creole_app(&block) mock_app do set :views, File.dirname(__FILE__) + '/views' get('/', &block) end get '/' end it 'renders inline creole strings' do creole_app { creole '= Hiya' } assert ok? assert_body "

Hiya

" end it 'renders .creole files in views path' do creole_app { creole :hello } assert ok? assert_body "

Hello From Creole

" end it "raises error if template not found" do mock_app { get('/') { creole :no_such_template } } assert_raise(Errno::ENOENT) { get('/') } end it "renders with inline layouts" do mock_app do layout { 'THIS. IS. #{yield.upcase}!' } get('/') { creole 'Sparta', :layout_engine => :str } end get '/' assert ok? assert_like 'THIS. IS.

SPARTA

!', body end it "renders with file layouts" do creole_app do creole 'Hello World', :layout => :layout2, :layout_engine => :erb end assert ok? assert_body "ERB Layout!\n

Hello World

" end it "can be used in a nested fashion for partials and whatnot" do mock_app do template(:inner) { "hi" } template(:outer) { "<%= creole :inner %>" } get('/') { erb :outer } end get '/' assert ok? assert_like '

hi

', body end end rescue LoadError warn "#{$!.to_s}: skipping creole tests" end sinatra-1.4.3/test/integration_test.rb0000644000004100000410000000505012161612727020103 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) require File.expand_path('../integration_helper', __FILE__) # These tests start a real server and talk to it over TCP. # Every test runs with every detected server. # # See test/integration/app.rb for the code of the app we test against. class IntegrationTest < Test::Unit::TestCase extend IntegrationHelper attr_accessor :server it('sets the app_file') { assert_equal server.app_file, server.get("/app_file") } it('only extends main') { assert_equal "true", server.get("/mainonly") } it 'logs once in development mode' do next if server.puma? or RUBY_ENGINE == 'jruby' random = "%064x" % Kernel.rand(2**256-1) server.get "/ping?x=#{random}" count = server.log.scan("GET /ping?x=#{random}").count if server.net_http_server? assert_equal 0, count elsif server.webrick? assert(count > 0) else assert_equal(1, count) end end it 'streams' do next if server.webrick? or server.trinidad? times, chunks = [Time.now], [] server.get_stream do |chunk| next if chunk.empty? chunks << chunk times << Time.now end assert_equal ["a", "b"], chunks assert times[1] - times[0] < 1 assert times[2] - times[1] > 1 end it 'streams async' do next unless server.thin? Timeout.timeout(3) do chunks = [] server.get_stream '/async' do |chunk| next if chunk.empty? chunks << chunk case chunk when "hi!" then server.get "/send?msg=hello" when "hello" then server.get "/send?close=1" end end assert_equal ['hi!', 'hello'], chunks end end it 'streams async from subclass' do next unless server.thin? Timeout.timeout(3) do chunks = [] server.get_stream '/subclass/async' do |chunk| next if chunk.empty? chunks << chunk case chunk when "hi!" then server.get "/subclass/send?msg=hello" when "hello" then server.get "/subclass/send?close=1" end end assert_equal ['hi!', 'hello'], chunks end end it 'starts the correct server' do exp = %r{ ==\sSinatra/#{Sinatra::VERSION}\s has\staken\sthe\sstage\son\s\d+\sfor\sdevelopment\s with\sbackup\sfrom\s#{server} }ix # because Net HTTP Server logs to $stderr by default assert_match exp, server.log unless server.net_http_server? end it 'does not generate warnings' do assert_raise(OpenURI::HTTPError) { server.get '/' } server.get '/app_file' assert_equal [], server.warnings end endsinatra-1.4.3/test/liquid_test.rb0000644000004100000410000000365512161612727017060 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) begin require 'liquid' class LiquidTest < Test::Unit::TestCase def liquid_app(&block) mock_app do set :views, File.dirname(__FILE__) + '/views' get('/', &block) end get '/' end it 'renders inline liquid strings' do liquid_app { liquid '

Hiya

' } assert ok? assert_equal "

Hiya

", body end it 'renders .liquid files in views path' do liquid_app { liquid :hello } assert ok? assert_equal "

Hello From Liquid

\n", body end it "renders with inline layouts" do mock_app do layout { "

THIS. IS. {{ yield }}

" } get('/') { liquid 'SPARTA' } end get '/' assert ok? assert_equal "

THIS. IS. SPARTA

", body end it "renders with file layouts" do liquid_app { liquid 'Hello World', :layout => :layout2 } assert ok? assert_equal "

Liquid Layout!

\n

Hello World

\n", body end it "raises error if template not found" do mock_app { get('/') { liquid :no_such_template } } assert_raise(Errno::ENOENT) { get('/') } end it "allows passing locals" do liquid_app { liquid '{{ value }}', :locals => { :value => 'foo' } } assert ok? assert_equal 'foo', body end it "can rendere truly nested layouts by accepting a layout and a block with the contents" do mock_app do template(:main_outer_layout) { "

Title

\n{{ yield }}" } template(:an_inner_layout) { "

Subtitle

\n{{ yield }}" } template(:a_page) { "

Contents.

\n" } get('/') do liquid :main_outer_layout, :layout => false do liquid :an_inner_layout do liquid :a_page end end end end get '/' assert ok? assert_body "

Title

\n

Subtitle

\n

Contents.

\n" end end rescue LoadError warn "#{$!.to_s}: skipping liquid tests" end sinatra-1.4.3/test/middleware_test.rb0000644000004100000410000000317212161612727017700 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) class MiddlewareTest < Test::Unit::TestCase setup do @app = mock_app(Sinatra::Application) do get('/*')do response.headers['X-Tests'] = env['test.ran']. map { |n| n.split('::').last }. join(', ') env['PATH_INFO'] end end end class MockMiddleware < Struct.new(:app) def call(env) (env['test.ran'] ||= []) << self.class.to_s app.call(env) end end class UpcaseMiddleware < MockMiddleware def call(env) env['PATH_INFO'] = env['PATH_INFO'].upcase super end end it "is added with Sinatra::Application.use" do @app.use UpcaseMiddleware get '/hello-world' assert ok? assert_equal '/HELLO-WORLD', body end class DowncaseMiddleware < MockMiddleware def call(env) env['PATH_INFO'] = env['PATH_INFO'].downcase super end end it "runs in the order defined" do @app.use UpcaseMiddleware @app.use DowncaseMiddleware get '/Foo' assert_equal "/foo", body assert_equal "UpcaseMiddleware, DowncaseMiddleware", response['X-Tests'] end it "resets the prebuilt pipeline when new middleware is added" do @app.use UpcaseMiddleware get '/Foo' assert_equal "/FOO", body @app.use DowncaseMiddleware get '/Foo' assert_equal '/foo', body assert_equal "UpcaseMiddleware, DowncaseMiddleware", response['X-Tests'] end it "works when app is used as middleware" do @app.use UpcaseMiddleware @app = @app.new get '/Foo' assert_equal "/FOO", body assert_equal "UpcaseMiddleware", response['X-Tests'] end end sinatra-1.4.3/test/yajl_test.rb0000644000004100000410000000350412161612727016521 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) begin require 'yajl' class YajlTest < Test::Unit::TestCase def yajl_app(&block) mock_app do set :views, File.dirname(__FILE__) + '/views' get('/', &block) end get '/' end it 'renders inline Yajl strings' do yajl_app { yajl('json = { :foo => "bar" }') } assert ok? assert_body '{"foo":"bar"}' end it 'renders .yajl files in views path' do yajl_app { yajl(:hello) } assert ok? assert_body '{"yajl":"hello"}' end it 'raises error if template not found' do mock_app { get('/') { yajl(:no_such_template) } } assert_raise(Errno::ENOENT) { get('/') } end it 'accepts a :locals option' do yajl_app do locals = { :object => { :foo => 'bar' } } yajl 'json = object', :locals => locals end assert ok? assert_body '{"foo":"bar"}' end it 'accepts a :scope option' do yajl_app do scope = { :object => { :foo => 'bar' } } yajl 'json = self[:object]', :scope => scope end assert ok? assert_body '{"foo":"bar"}' end it 'decorates the json with a callback' do yajl_app do yajl( 'json = { :foo => "bar" }', { :callback => 'baz' } ) end assert ok? assert_body 'baz({"foo":"bar"});' end it 'decorates the json with a variable' do yajl_app do yajl( 'json = { :foo => "bar" }', { :variable => 'qux' } ) end assert ok? assert_body 'var qux = {"foo":"bar"};' end it 'decorates the json with a callback and a variable' do yajl_app do yajl( 'json = { :foo => "bar" }', { :callback => 'baz', :variable => 'qux' } ) end assert ok? assert_body 'var qux = {"foo":"bar"}; baz(qux);' end end rescue LoadError warn "#{$!.to_s}: skipping yajl tests" end sinatra-1.4.3/test/sinatra_test.rb0000644000004100000410000000055412161612727017225 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) class SinatraTest < Test::Unit::TestCase it 'creates a new Sinatra::Base subclass on new' do app = Sinatra.new { get('/') { 'Hello World' } } assert_same Sinatra::Base, app.superclass end it "responds to #template_cache" do assert_kind_of Tilt::Cache, Sinatra::Base.new!.template_cache end end sinatra-1.4.3/test/markaby_test.rb0000644000004100000410000000373412161612727017215 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) begin require 'markaby' class MarkabyTest < Test::Unit::TestCase def markaby_app(&block) mock_app do set :views, File.dirname(__FILE__) + '/views' get('/', &block) end get '/' end it 'renders inline markaby strings' do markaby_app { markaby 'h1 "Hiya"' } assert ok? assert_equal "

Hiya

", body end it 'renders .markaby files in views path' do markaby_app { markaby :hello } assert ok? assert_equal "

Hello From Markaby

", body end it "renders with inline layouts" do mock_app do layout { 'h1 { text "THIS. IS. "; yield }' } get('/') { markaby 'em "SPARTA"' } end get '/' assert ok? assert_equal "

THIS. IS. SPARTA

", body end it "renders with file layouts" do markaby_app { markaby 'text "Hello World"', :layout => :layout2 } assert ok? assert_equal "

Markaby Layout!

Hello World

", body end it 'renders inline markaby blocks' do markaby_app { markaby { h1 'Hiya' } } assert ok? assert_equal "

Hiya

", body end it 'renders inline markaby blocks with inline layouts' do markaby_app do settings.layout { 'h1 { text "THIS. IS. "; yield }' } markaby { em 'SPARTA' } end assert ok? assert_equal "

THIS. IS. SPARTA

", body end it 'renders inline markaby blocks with file layouts' do markaby_app { markaby(:layout => :layout2) { text "Hello World" } } assert ok? assert_equal "

Markaby Layout!

Hello World

", body end it "raises error if template not found" do mock_app { get('/') { markaby :no_such_template } } assert_raise(Errno::ENOENT) { get('/') } end it "allows passing locals" do markaby_app { markaby 'text value', :locals => { :value => 'foo' } } assert ok? assert_equal 'foo', body end end rescue LoadError warn "#{$!.to_s}: skipping markaby tests" end sinatra-1.4.3/test/helper.rb0000644000004100000410000000612512161612727016004 0ustar www-datawww-dataENV['RACK_ENV'] = 'test' Encoding.default_external = "UTF-8" if defined? Encoding RUBY_ENGINE = 'ruby' unless defined? RUBY_ENGINE begin require 'rack' rescue LoadError require 'rubygems' require 'rack' end testdir = File.dirname(__FILE__) $LOAD_PATH.unshift testdir unless $LOAD_PATH.include?(testdir) libdir = File.dirname(File.dirname(__FILE__)) + '/lib' $LOAD_PATH.unshift libdir unless $LOAD_PATH.include?(libdir) require 'contest' require 'rack/test' require 'sinatra/base' class Sinatra::Base # Allow assertions in request context include Test::Unit::Assertions end class Rack::Builder def include?(middleware) @ins.any? { |m| p m ; middleware === m } end end Sinatra::Base.set :environment, :test class Test::Unit::TestCase include Rack::Test::Methods class << self alias_method :it, :test alias_method :section, :context end def self.example(desc = nil, &block) @example_count = 0 unless instance_variable_defined? :@example_count @example_count += 1 it(desc || "Example #{@example_count}", &block) end alias_method :response, :last_response setup do Sinatra::Base.set :environment, :test end # Sets up a Sinatra::Base subclass defined with the block # given. Used in setup or individual spec methods to establish # the application. def mock_app(base=Sinatra::Base, &block) @app = Sinatra.new(base, &block) end def app Rack::Lint.new(@app) end def body response.body.to_s end def assert_body(value) if value.respond_to? :to_str assert_equal value.lstrip.gsub(/\s*\n\s*/, ""), body.lstrip.gsub(/\s*\n\s*/, "") else assert_match value, body end end def assert_status(expected) assert_equal Integer(expected), Integer(status) end def assert_like(a,b) pattern = /id=['"][^"']*["']|\s+/ assert_equal a.strip.gsub(pattern, ""), b.strip.gsub(pattern, "") end def assert_include(str, substr) assert str.include?(substr), "expected #{str.inspect} to include #{substr.inspect}" end def options(uri, params = {}, env = {}, &block) request(uri, env.merge(:method => "OPTIONS", :params => params), &block) end def patch(uri, params = {}, env = {}, &block) request(uri, env.merge(:method => "PATCH", :params => params), &block) end def link(uri, params = {}, env = {}, &block) request(uri, env.merge(:method => "LINK", :params => params), &block) end def unlink(uri, params = {}, env = {}, &block) request(uri, env.merge(:method => "UNLINK", :params => params), &block) end # Delegate other missing methods to response. def method_missing(name, *args, &block) if response && response.respond_to?(name) response.send(name, *args, &block) else super end rescue Rack::Test::Error super end # Also check response since we delegate there. def respond_to?(symbol, include_private=false) super || (response && response.respond_to?(symbol, include_private)) end # Do not output warnings for the duration of the block. def silence_warnings $VERBOSE, v = nil, $VERBOSE yield ensure $VERBOSE = v end end sinatra-1.4.3/test/mapped_error_test.rb0000644000004100000410000001630312161612727020242 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) class FooError < RuntimeError end class FooNotFound < Sinatra::NotFound end class FooSpecialError < RuntimeError def http_status; 501 end end class FooStatusOutOfRangeError < RuntimeError def code; 4000 end end class FooWithCode < RuntimeError def code; 419 end end class FirstError < RuntimeError; end class SecondError < RuntimeError; end class MappedErrorTest < Test::Unit::TestCase def test_default assert true end describe 'Exception Mappings' do it 'invokes handlers registered with ::error when raised' do mock_app do set :raise_errors, false error(FooError) { 'Foo!' } get('/') { raise FooError } end get '/' assert_equal 500, status 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 do set :raise_errors, false error(Exception) { 'Exception!' } get('/') { raise FooError } end get '/' assert_equal 500, status assert_equal 'Exception!', body end it 'walks down inheritance chain for errors' do mock_app do set :raise_errors, false error(RuntimeError) { 'Exception!' } get('/') { raise FooError } end get '/' assert_equal 500, status assert_equal 'Exception!', body end it 'favors subclass handler over superclass handler if available' do mock_app do set :raise_errors, false error(Exception) { 'Exception!' } error(FooError) { 'FooError!' } error(RuntimeError) { 'Exception!' } get('/') { raise FooError } end get '/' assert_equal 500, status assert_equal 'FooError!', body end it "sets env['sinatra.error'] to the rescued exception" do mock_app do set :raise_errors, false error(FooError) do assert env.include?('sinatra.error') assert env['sinatra.error'].kind_of?(FooError) 'looks good' end get('/') { raise FooError } end get '/' assert_equal 'looks good', body end it "raises errors from the app when raise_errors set and no handler defined" do mock_app do set :raise_errors, true get('/') { raise FooError } end assert_raise(FooError) { get '/' } end it "calls error handlers before raising errors even when raise_errors is set" do mock_app do set :raise_errors, true error(FooError) { "she's there." } get('/') { raise FooError } end assert_nothing_raised { get '/' } assert_equal 500, status end it "never raises Sinatra::NotFound beyond the application" do mock_app(Sinatra::Application) do get('/') { raise Sinatra::NotFound } end assert_nothing_raised { get '/' } assert_equal 404, status end it "cascades for subclasses of Sinatra::NotFound" do mock_app do set :raise_errors, true error(FooNotFound) { "foo! not found." } get('/') { raise FooNotFound } end assert_nothing_raised { get '/' } assert_equal 404, status assert_equal 'foo! not found.', body end it 'has a not_found method for backwards compatibility' do mock_app { not_found { "Lost, are we?" } } get '/test' assert_equal 404, status assert_equal "Lost, are we?", body end it 'inherits error mappings from base class' do base = Class.new(Sinatra::Base) base.error(FooError) { 'base class' } mock_app(base) do set :raise_errors, false get('/') { raise FooError } end get '/' assert_equal 'base class', body end it 'overrides error mappings in base class' do base = Class.new(Sinatra::Base) base.error(FooError) { 'base class' } mock_app(base) do set :raise_errors, false error(FooError) { 'subclass' } get('/') { raise FooError } end get '/' assert_equal 'subclass', body end it 'honors Exception#http_status if present' do mock_app do set :raise_errors, false error(501) { 'Foo!' } get('/') { raise FooSpecialError } end get '/' assert_equal 501, status assert_equal 'Foo!', body end it 'does not use Exception#code by default' do mock_app do set :raise_errors, false get('/') { raise FooWithCode } end get '/' assert_equal 500, status end it 'uses Exception#code if use_code is enabled' do mock_app do set :raise_errors, false set :use_code, true get('/') { raise FooWithCode } end get '/' assert_equal 419, status end it 'does not rely on Exception#code for invalid codes' do mock_app do set :raise_errors, false set :use_code, true get('/') { raise FooStatusOutOfRangeError } end get '/' assert_equal 500, status end it "allows a stack of exception_handlers" do mock_app do set :raise_errors, false error(FirstError) { 'First!' } error(SecondError) { 'Second!' } get('/'){ raise SecondError } end get '/' assert_equal 500, status assert_equal 'Second!', body end it "allows an exception handler to pass control to the next exception handler" do mock_app do set :raise_errors, false error(500, FirstError) { 'First!' } error(500, SecondError) { pass } get('/') { raise 500 } end get '/' assert_equal 500, status assert_equal 'First!', body end it "allows an exception handler to handle the exception" do mock_app do set :raise_errors, false error(500, FirstError) { 'First!' } error(500, SecondError) { 'Second!' } get('/') { raise 500 } end get '/' assert_equal 500, status assert_equal 'Second!', body end end describe 'Custom Error Pages' do it 'allows numeric status code mappings to be registered with ::error' do mock_app do set :raise_errors, false error(500) { 'Foo!' } get('/') { [500, {}, 'Internal Foo Error'] } end get '/' assert_equal 500, status assert_equal 'Foo!', body end it 'allows ranges of status code mappings to be registered with :error' do mock_app do set :raise_errors, false error(500..550) { "Error: #{response.status}" } get('/') { [507, {}, 'A very special error'] } end get '/' assert_equal 507, status assert_equal 'Error: 507', body end it 'allows passing more than one range' do mock_app do set :raise_errors, false error(409..411, 503..509) { "Error: #{response.status}" } get('/') { [507, {}, 'A very special error'] } end get '/' assert_equal 507, status assert_equal 'Error: 507', body end end end sinatra-1.4.3/test/settings_test.rb0000644000004100000410000003557212161612727017434 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) class SettingsTest < Test::Unit::TestCase setup do @base = Sinatra.new(Sinatra::Base) @base.set :environment => :foo, :app_file => nil @application = Sinatra.new(Sinatra::Application) @application.set :environment => :foo, :app_file => nil end it 'sets settings to literal values' do @base.set(:foo, 'bar') assert @base.respond_to?(:foo) assert_equal 'bar', @base.foo end it 'sets settings to Procs' do @base.set(:foo, Proc.new { 'baz' }) assert @base.respond_to?(:foo) assert_equal 'baz', @base.foo end it 'sets settings using a block' do @base.set(:foo){ 'baz' } assert @base.respond_to?(:foo) assert_equal 'baz', @base.foo end it 'raises an error with a value and a block' do assert_raise ArgumentError do @base.set(:fiz, 'boom!'){ 'baz' } end assert !@base.respond_to?(:fiz) end it 'raises an error without value and block' do assert_raise(ArgumentError) { @base.set(:fiz) } assert !@base.respond_to?(:fiz) end it 'allows setting a value to the app class' do @base.set :base, @base assert @base.respond_to?(:base) assert_equal @base, @base.base end it 'raises an error with the app class as value and a block' do assert_raise ArgumentError do @base.set(:fiz, @base) { 'baz' } end assert !@base.respond_to?(:fiz) end it "sets multiple settings with a Hash" do @base.set :foo => 1234, :bar => 'Hello World', :baz => Proc.new { 'bizzle' } assert_equal 1234, @base.foo assert_equal 'Hello World', @base.bar assert_equal 'bizzle', @base.baz end it 'sets multiple settings using #each' do @base.set [["foo", "bar"]] assert_equal "bar", @base.foo end it 'inherits settings methods when subclassed' do @base.set :foo, 'bar' @base.set :biz, Proc.new { 'baz' } sub = Class.new(@base) assert sub.respond_to?(:foo) assert_equal 'bar', sub.foo assert sub.respond_to?(:biz) assert_equal 'baz', sub.biz end it 'overrides settings in subclass' do @base.set :foo, 'bar' @base.set :biz, Proc.new { 'baz' } sub = Class.new(@base) sub.set :foo, 'bling' assert_equal 'bling', sub.foo assert_equal 'bar', @base.foo end it 'creates setter methods when first defined' do @base.set :foo, 'bar' assert @base.respond_to?('foo=') @base.foo = 'biz' assert_equal 'biz', @base.foo end it 'creates predicate methods when first defined' do @base.set :foo, 'hello world' assert @base.respond_to?(:foo?) assert @base.foo? @base.set :foo, nil assert !@base.foo? end it 'uses existing setter methods if detected' do class << @base def foo @foo end def foo=(value) @foo = 'oops' end end @base.set :foo, 'bam' assert_equal 'oops', @base.foo end it 'merges values of multiple set calls if those are hashes' do @base.set :foo, :a => 1 sub = Class.new(@base) sub.set :foo, :b => 2 assert_equal({:a => 1, :b => 2}, sub.foo) end it 'merging does not affect the superclass' do @base.set :foo, :a => 1 sub = Class.new(@base) sub.set :foo, :b => 2 assert_equal({:a => 1}, @base.foo) end it 'is possible to change a value from a hash to something else' do @base.set :foo, :a => 1 @base.set :foo, :bar assert_equal(:bar, @base.foo) end it 'merges values with values of the superclass if those are hashes' do @base.set :foo, :a => 1 @base.set :foo, :b => 2 assert_equal({:a => 1, :b => 2}, @base.foo) end it "sets multiple settings to true with #enable" do @base.enable :sessions, :foo, :bar assert @base.sessions assert @base.foo assert @base.bar end it "sets multiple settings to false with #disable" do @base.disable :sessions, :foo, :bar assert !@base.sessions assert !@base.foo assert !@base.bar end it 'is accessible from instances via #settings' do assert_equal :foo, @base.new!.settings.environment end it 'is accessible from class via #settings' do assert_equal :foo, @base.settings.environment end describe 'methodoverride' do it 'is disabled on Base' do assert ! @base.method_override? end it 'is enabled on Application' do assert @application.method_override? end it 'enables MethodOverride middleware' do @base.set :method_override, true @base.put('/') { 'okay' } @app = @base post '/', {'_method'=>'PUT'}, {} assert_equal 200, status assert_equal 'okay', body end it 'is backward compatible with methodoverride' do assert ! @base.methodoverride? @base.enable :methodoverride assert @base.methodoverride? end end describe 'run' do it 'is disabled on Base' do assert ! @base.run? end it 'is enabled on Application except in test environment' do assert @application.run? @application.set :environment, :test assert ! @application.run? end end describe 'raise_errors' do it 'is enabled on Base only in test' do assert ! @base.raise_errors? @base.set(:environment, :test) assert @base.raise_errors? end it 'is enabled on Application only in test' do assert ! @application.raise_errors? @application.set(:environment, :test) assert @application.raise_errors? end end describe 'show_exceptions' do it 'is disabled on Base except under development' do assert ! @base.show_exceptions? @base.environment = :development assert @base.show_exceptions? end it 'is disabled on Application except in development' do assert ! @application.show_exceptions? @application.set(:environment, :development) assert @application.show_exceptions? end it 'returns a friendly 500' do klass = Sinatra.new(Sinatra::Application) mock_app(klass) { enable :show_exceptions get '/' do raise StandardError end } get '/' assert_equal 500, status assert body.include?("StandardError") assert body.include?("show_exceptions setting") end it 'does not override app-specified error handling when set to :after_handler' do ran = false mock_app do set :show_exceptions, :after_handler error(RuntimeError) { ran = true } get('/') { raise RuntimeError } end get '/' assert_equal 500, status assert ran end it 'does catch any other exceptions when set to :after_handler' do ran = false mock_app do set :show_exceptions, :after_handler error(RuntimeError) { ran = true } get('/') { raise ArgumentError } end get '/' assert_equal 500, status assert !ran end end describe 'dump_errors' do it 'is disabled on Base in test' do @base.environment = :test assert ! @base.dump_errors? @base.environment = :development assert @base.dump_errors? @base.environment = :production assert @base.dump_errors? end it 'dumps exception with backtrace to rack.errors' do klass = Sinatra.new(Sinatra::Application) mock_app(klass) { enable :dump_errors disable :raise_errors error do error = @env['rack.errors'].instance_variable_get(:@error) error.rewind error.read end get '/' do raise end } get '/' assert body.include?("RuntimeError") && body.include?("settings_test.rb") end it 'does not dump 404 errors' do klass = Sinatra.new(Sinatra::Application) mock_app(klass) { enable :dump_errors disable :raise_errors error do error = @env['rack.errors'].instance_variable_get(:@error) error.rewind error.read end get '/' do raise Sinatra::NotFound end } get '/' assert !body.include?("NotFound") && !body.include?("settings_test.rb") end end describe 'sessions' do it 'is disabled on Base' do assert ! @base.sessions? end it 'is disabled on Application' do assert ! @application.sessions? end end describe 'logging' do it 'is disabled on Base' do assert ! @base.logging? end it 'is enabled on Application except in test environment' do assert @application.logging? @application.set :environment, :test assert ! @application.logging end end describe 'static' do it 'is disabled on Base by default' do assert ! @base.static? end it 'is enabled on Base when public_folder is set and exists' do @base.set :environment, :development @base.set :public_folder, File.dirname(__FILE__) assert @base.static? end it 'is enabled on Base when root is set and root/public_folder exists' do @base.set :environment, :development @base.set :root, File.dirname(__FILE__) assert @base.static? end it 'is disabled on Application by default' do assert ! @application.static? end it 'is enabled on Application when public_folder is set and exists' do @application.set :environment, :development @application.set :public_folder, File.dirname(__FILE__) assert @application.static? end it 'is enabled on Application when root is set and root/public_folder exists' do @application.set :environment, :development @application.set :root, File.dirname(__FILE__) assert @application.static? end it 'is possible to use Module#public' do @base.send(:define_method, :foo) { } @base.send(:private, :foo) assert !@base.public_method_defined?(:foo) @base.send(:public, :foo) assert @base.public_method_defined?(:foo) end it 'is possible to use the keyword public in a sinatra app' do app = Sinatra.new do private def priv; end public def pub; end end assert !app.public_method_defined?(:priv) assert app.public_method_defined?(:pub) end end describe 'bind' do it 'defaults to 0.0.0.0' do assert_equal '0.0.0.0', @base.bind assert_equal '0.0.0.0', @application.bind end end describe 'port' do it 'defaults to 4567' do assert_equal 4567, @base.port assert_equal 4567, @application.port end end describe 'server' do it 'includes webrick' do assert @base.server.include?('webrick') assert @application.server.include?('webrick') end it 'includes puma' do assert @base.server.include?('puma') assert @application.server.include?('puma') end it 'includes thin' do next if RUBY_ENGINE == 'jruby' assert @base.server.include?('thin') assert @application.server.include?('thin') end end describe 'app_file' do it 'is nil for base classes' do assert_nil Sinatra::Base.app_file assert_nil Sinatra::Application.app_file end it 'defaults to the file subclassing' do assert_equal File.expand_path(__FILE__), Sinatra.new.app_file end end describe 'root' do it 'is nil if app_file is not set' do assert @base.root.nil? assert @application.root.nil? end it 'is equal to the expanded basename of app_file' do @base.app_file = __FILE__ assert_equal File.expand_path(File.dirname(__FILE__)), @base.root @application.app_file = __FILE__ assert_equal File.expand_path(File.dirname(__FILE__)), @application.root end end describe 'views' do it 'is nil if root is not set' do assert @base.views.nil? assert @application.views.nil? end it 'is set to root joined with views/' do @base.root = File.dirname(__FILE__) assert_equal File.dirname(__FILE__) + "/views", @base.views @application.root = File.dirname(__FILE__) assert_equal File.dirname(__FILE__) + "/views", @application.views end end describe 'public_folder' do it 'is nil if root is not set' do assert @base.public_folder.nil? assert @application.public_folder.nil? end it 'is set to root joined with public/' do @base.root = File.dirname(__FILE__) assert_equal File.dirname(__FILE__) + "/public", @base.public_folder @application.root = File.dirname(__FILE__) assert_equal File.dirname(__FILE__) + "/public", @application.public_folder end end describe 'public_dir' do it 'is an alias for public_folder' do @base.public_dir = File.dirname(__FILE__) assert_equal File.dirname(__FILE__), @base.public_dir assert_equal @base.public_folder, @base.public_dir @application.public_dir = File.dirname(__FILE__) assert_equal File.dirname(__FILE__), @application.public_dir assert_equal @application.public_folder, @application.public_dir end end describe 'lock' do it 'is disabled by default' do assert ! @base.lock? 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 it 'sets up RemoteToken if sessions are enabled' do MiddlewareTracker.track do Sinatra.new { enable :sessions }.new assert_include MiddlewareTracker.used, Rack::Protection::RemoteToken end end it 'does not set up RemoteToken if sessions are disabled' do MiddlewareTracker.track do Sinatra.new.new assert !MiddlewareTracker.used.include?(Rack::Protection::RemoteToken) end end it 'sets up RemoteToken if it is configured to' do MiddlewareTracker.track do Sinatra.new { set :protection, :session => true }.new assert_include MiddlewareTracker.used, Rack::Protection::RemoteToken end end end end sinatra-1.4.3/test/less_test.rb0000644000004100000410000000347512161612727016537 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) begin require 'less' class LessTest < Test::Unit::TestCase def less_app(options = {}, &block) mock_app do set :views, File.dirname(__FILE__) + '/views' set options get('/', &block) end get '/' end it 'renders inline Less strings' do less_app { less "@white_color: #fff; #main { background-color: @white_color }" } assert ok? assert_equal "#main{background-color:#ffffff;}", body.gsub(/\s/, "") end it 'defaults content type to css' do less_app { less "@white_color: #fff; #main { background-color: @white_color }" } assert ok? assert_equal "text/css;charset=utf-8", response['Content-Type'] end it 'defaults allows setting content type per route' do less_app do content_type :html less "@white_color: #fff; #main { background-color: @white_color }" end assert ok? assert_equal "text/html;charset=utf-8", response['Content-Type'] end it 'defaults allows setting content type globally' do less_app(:less => { :content_type => 'html' }) do less "@white_color: #fff; #main { background-color: @white_color }" end assert ok? assert_equal "text/html;charset=utf-8", response['Content-Type'] end it 'renders .less files in views path' do less_app { less :hello } assert ok? assert_equal "#main{background-color:#ffffff;}", body.gsub(/\s/, "") end it 'ignores the layout option' do less_app { less :hello, :layout => :layout2 } assert ok? assert_equal "#main{background-color:#ffffff;}", body.gsub(/\s/, "") end it "raises error if template not found" do mock_app { get('/') { less :no_such_template } } assert_raise(Errno::ENOENT) { get('/') } end end rescue LoadError warn "#{$!.to_s}: skipping less tests" end sinatra-1.4.3/test/markdown_test.rb0000644000004100000410000000403212161612727017401 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) MarkdownTest = proc do def markdown_app(&block) mock_app do set :views, File.dirname(__FILE__) + '/views' get('/', &block) end get '/' end def setup Tilt.prefer engine, 'markdown', 'mkd', 'md' super end it 'uses the correct engine' do assert_equal engine, Tilt[:md] assert_equal engine, Tilt[:mkd] assert_equal engine, Tilt[:markdown] end it 'renders inline markdown strings' do markdown_app { markdown '# Hiya' } assert ok? assert_like "

Hiya

\n", body end it 'renders .markdown files in views path' do markdown_app { markdown :hello } assert ok? assert_like "

Hello From Markdown

", body end it "raises error if template not found" do mock_app { get('/') { markdown :no_such_template } } assert_raise(Errno::ENOENT) { get('/') } end it "renders with inline layouts" do mock_app do layout { 'THIS. IS. #{yield.upcase}!' } get('/') { markdown 'Sparta', :layout_engine => :str } end get '/' assert ok? assert_like 'THIS. IS.

SPARTA

!', body end it "renders with file layouts" do markdown_app { markdown 'Hello World', :layout => :layout2, :layout_engine => :erb } assert ok? assert_body "ERB Layout!\n

Hello World

" end it "can be used in a nested fashion for partials and whatnot" do mock_app do template(:inner) { "hi" } template(:outer) { "<%= markdown :inner %>" } get('/') { erb :outer } end get '/' assert ok? assert_like '

hi

', body end end # Will generate RDiscountTest, KramdownTest, etc. Tilt.mappings['md'].each do |t| begin t.new { "" } klass = Class.new(Test::Unit::TestCase) { define_method(:engine) { t }} klass.class_eval(&MarkdownTest) name = t.name[/[^:]+$/].sub(/Template$/, '') << "Test" Object.const_set name, klass rescue LoadError, NameError warn "#{$!}: skipping markdown tests with #{t}" end end sinatra-1.4.3/test/routing_test.rb0000644000004100000410000007745512161612727017271 0ustar www-datawww-data# I like coding: UTF-8 require File.expand_path('../helper', __FILE__) # Helper method for easy route pattern matching testing def route_def(pattern) mock_app { get(pattern) { } } end class RegexpLookAlike class MatchData def captures ["this", "is", "a", "test"] end end def match(string) ::RegexpLookAlike::MatchData.new if string == "/this/is/a/test/" end def keys ["one", "two", "three", "four"] end end class RoutingTest < Test::Unit::TestCase %w[get put post delete options patch link unlink].each do |verb| it "defines #{verb.upcase} request handlers with #{verb}" do mock_app { send verb, '/hello' do 'Hello World' end } request = Rack::MockRequest.new(@app) response = request.request(verb.upcase, '/hello', {}) assert response.ok? assert_equal 'Hello World', response.body end end it "defines HEAD request handlers with HEAD" do mock_app { head '/hello' do response['X-Hello'] = 'World!' 'remove me' end } request = Rack::MockRequest.new(@app) response = request.request('HEAD', '/hello', {}) assert response.ok? assert_equal 'World!', response['X-Hello'] assert_equal '', response.body end it "404s when no route satisfies the request" do mock_app { get('/foo') { } } get '/bar' assert_equal 404, status end it "404s and sets X-Cascade header when no route satisfies the request" do mock_app { get('/foo') { } } get '/bar' assert_equal 404, status assert_equal 'pass', response.headers['X-Cascade'] end it "404s and does not set X-Cascade header when no route satisfies the request and x_cascade has been disabled" do mock_app { disable :x_cascade get('/foo') { } } get '/bar' assert_equal 404, status assert_equal nil, response.headers['X-Cascade'] end it "allows using unicode" do mock_app do get('/föö') { } end get '/f%C3%B6%C3%B6' assert_equal 200, status end it "it handles encoded slashes correctly" do mock_app { get("/:a") { |a| a } } get '/foo%2Fbar' assert_equal 200, status assert_body "foo/bar" end it "overrides the content-type in error handlers" do mock_app { before { content_type 'text/plain' } error Sinatra::NotFound do content_type "text/html" "

Not Found

" end } get '/foo' assert_equal 404, status assert_equal 'text/html;charset=utf-8', response["Content-Type"] assert_equal "

Not Found

", response.body end it "recalculates body length correctly for 404 response" do mock_app { get '/' do @response["Content-Length"] = "30" raise Sinatra::NotFound end } get "/" assert_equal "18", response["Content-Length"] assert_equal 404, status end it 'matches empty PATH_INFO to "/" if no route is defined for ""' do mock_app do get '/' do 'worked' end end get '/', {}, "PATH_INFO" => "" assert ok? assert_equal 'worked', body end it 'matches empty PATH_INFO to "" if a route is defined for ""' do mock_app do disable :protection get '/' do 'did not work' end get '' do 'worked' end end get '/', {}, "PATH_INFO" => "" assert ok? assert_equal 'worked', body end it 'takes multiple definitions of a route' do mock_app { user_agent(/Foo/) get '/foo' do 'foo' end get '/foo' do 'not foo' end } get '/foo', {}, 'HTTP_USER_AGENT' => 'Foo' assert ok? assert_equal 'foo', body get '/foo' assert ok? assert_equal 'not foo', body end it "exposes params with indifferent hash" do mock_app { get '/:foo' do assert_equal 'bar', params['foo'] assert_equal 'bar', params[:foo] 'well, alright' end } get '/bar' assert_equal 'well, alright', body end it "merges named params and query string params in params" do mock_app { get '/:foo' do assert_equal 'bar', params['foo'] assert_equal 'biz', params['baz'] end } get '/bar?baz=biz' assert ok? end it "supports named params like /hello/:person" do mock_app { get '/hello/:person' do "Hello #{params['person']}" end } get '/hello/Frank' assert_equal 'Hello Frank', body end it "supports optional named params like /?:foo?/?:bar?" do mock_app { get '/?:foo?/?:bar?' do "foo=#{params[:foo]};bar=#{params[:bar]}" end } get '/hello/world' assert ok? assert_equal "foo=hello;bar=world", body get '/hello' assert ok? assert_equal "foo=hello;bar=", body get '/' assert ok? assert_equal "foo=;bar=", body end it "supports named captures like %r{/hello/(?[^/?#]+)} on Ruby >= 1.9" do next if RUBY_VERSION < '1.9' mock_app { get Regexp.new('/hello/(?[^/?#]+)') do "Hello #{params['person']}" end } get '/hello/Frank' assert_equal 'Hello Frank', body end it "supports optional named captures like %r{/page(?.[^/?#]+)?} on Ruby >= 1.9" do next if RUBY_VERSION < '1.9' mock_app { get Regexp.new('/page(?.[^/?#]+)?') do "format=#{params[:format]}" end } get '/page.html' assert ok? assert_equal "format=.html", body get '/page.xml' assert ok? assert_equal "format=.xml", body get '/page' assert ok? assert_equal "format=", body end it 'does not concatinate params with the same name' do mock_app { get('/:foo') { params[:foo] } } get '/a?foo=b' assert_body 'a' end it "supports single splat params like /*" do mock_app { get '/*' do assert params['splat'].kind_of?(Array) params['splat'].join "\n" end } get '/foo' assert_equal "foo", body get '/foo/bar/baz' assert_equal "foo/bar/baz", body end it "supports mixing multiple splat params like /*/foo/*/*" do mock_app { get '/*/foo/*/*' do assert params['splat'].kind_of?(Array) params['splat'].join "\n" end } get '/bar/foo/bling/baz/boom' assert_equal "bar\nbling\nbaz/boom", body get '/bar/foo/baz' assert not_found? end it "supports mixing named and splat params like /:foo/*" do mock_app { get '/:foo/*' do assert_equal 'foo', params['foo'] assert_equal ['bar/baz'], params['splat'] end } get '/foo/bar/baz' assert ok? end it "matches a dot ('.') as part of a named param" do mock_app { get '/:foo/:bar' do params[:foo] end } get '/user@example.com/name' assert_equal 200, response.status assert_equal 'user@example.com', body end it "matches a literal dot ('.') outside of named params" do mock_app { get '/:file.:ext' do assert_equal 'pony', params[:file] assert_equal 'jpg', params[:ext] 'right on' end } get '/pony.jpg' assert_equal 200, response.status assert_equal 'right on', body end it "literally matches dot in paths" do route_def '/test.bar' get '/test.bar' assert ok? get 'test0bar' assert not_found? end it "literally matches dollar sign in paths" do route_def '/test$/' get '/test$/' assert ok? end it "literally matches plus sign in paths" do route_def '/te+st/' get '/te%2Bst/' assert ok? get '/teeeeeeest/' assert not_found? end it "does not convert plus sign into space as the value of a named param" do mock_app do get '/:test' do params["test"] end end get '/bob+ross' assert ok? assert_equal 'bob+ross', body end it "literally matches parens in paths" do route_def '/test(bar)/' get '/test(bar)/' assert ok? end it "supports basic nested params" do mock_app { get '/hi' do params["person"]["name"] end } get "/hi?person[name]=John+Doe" assert ok? assert_equal "John Doe", body end it "exposes nested params with indifferent hash" do mock_app { get '/testme' do assert_equal 'baz', params['bar']['foo'] assert_equal 'baz', params['bar'][:foo] 'well, alright' end } get '/testme?bar[foo]=baz' assert_equal 'well, alright', body end it "exposes params nested within arrays with indifferent hash" do mock_app { get '/testme' do assert_equal 'baz', params['bar'][0]['foo'] assert_equal 'baz', params['bar'][0][:foo] 'well, alright' end } get '/testme?bar[][foo]=baz' assert_equal 'well, alright', body end it "supports arrays within params" do mock_app { get '/foo' do assert_equal ['A', 'B'], params['bar'] 'looks good' end } get '/foo?bar[]=A&bar[]=B' assert ok? assert_equal 'looks good', body end it "supports deeply nested params" do expected_params = { "emacs" => { "map" => { "goto-line" => "M-g g" }, "version" => "22.3.1" }, "browser" => { "firefox" => {"engine" => {"name"=>"spidermonkey", "version"=>"1.7.0"}}, "chrome" => {"engine" => {"name"=>"V8", "version"=>"1.0"}} }, "paste" => {"name"=>"hello world", "syntax"=>"ruby"} } mock_app { get '/foo' do assert_equal expected_params, params 'looks good' end } get '/foo', expected_params assert ok? assert_equal 'looks good', body end it "preserves non-nested params" do mock_app { get '/foo' do assert_equal "2", params["article_id"] assert_equal "awesome", params['comment']['body'] assert_nil params['comment[body]'] 'looks good' end } get '/foo?article_id=2&comment[body]=awesome' assert ok? assert_equal 'looks good', body end it "matches paths that include spaces encoded with %20" do mock_app { get '/path with spaces' do 'looks good' end } get '/path%20with%20spaces' assert ok? assert_equal 'looks good', body end it "matches paths that include spaces encoded with +" do mock_app { get '/path with spaces' do 'looks good' end } get '/path+with+spaces' assert ok? assert_equal 'looks good', body end it "matches paths that include ampersands" do mock_app { get '/:name' do 'looks good' end } get '/foo&bar' assert ok? assert_equal 'looks good', body end it "URL decodes named parameters and splats" do mock_app { get '/:foo/*' do assert_equal 'hello world', params['foo'] assert_equal ['how are you'], params['splat'] nil end } get '/hello%20world/how%20are%20you' assert ok? end it 'supports regular expressions' do mock_app { get(/^\/foo...\/bar$/) do 'Hello World' end } get '/foooom/bar' assert ok? assert_equal 'Hello World', body end it 'makes regular expression captures available in params[:captures]' do mock_app { get(/^\/fo(.*)\/ba(.*)/) do assert_equal ['orooomma', 'f'], params[:captures] 'right on' end } get '/foorooomma/baf' assert ok? assert_equal 'right on', body end it 'supports regular expression look-alike routes' do mock_app { get(RegexpLookAlike.new) do assert_equal 'this', params[:one] assert_equal 'is', params[:two] assert_equal 'a', params[:three] assert_equal 'test', params[:four] 'right on' end } get '/this/is/a/test/' assert ok? assert_equal 'right on', body end it 'raises a TypeError when pattern is not a String or Regexp' do assert_raise(TypeError) { mock_app { get(42){} } } end it "returns response immediately on halt" do mock_app { get '/' do halt 'Hello World' 'Boo-hoo World' end } get '/' assert ok? assert_equal 'Hello World', body end it "halts with a response tuple" do mock_app { get '/' do halt 295, {'Content-Type' => 'text/plain'}, 'Hello World' end } get '/' assert_equal 295, status assert_equal 'text/plain', response['Content-Type'] assert_equal 'Hello World', body end it "halts with an array of strings" do mock_app { get '/' do halt %w[Hello World How Are You] end } get '/' assert_equal 'HelloWorldHowAreYou', body end it 'sets response.status with halt' do status_was = nil mock_app do after { status_was = status } get('/') { halt 500, 'error' } end get '/' assert_status 500 assert_equal 500, status_was end it "transitions to the next matching route on pass" do mock_app { get '/:foo' do pass 'Hello Foo' end get '/*' do assert !params.include?('foo') 'Hello World' end } get '/bar' assert ok? assert_equal 'Hello World', body end it "transitions to 404 when passed and no subsequent route matches" do mock_app { get '/:foo' do pass 'Hello Foo' end } get '/bar' assert not_found? end it "transitions to 404 and sets X-Cascade header when passed and no subsequent route matches" do mock_app { get '/:foo' do pass 'Hello Foo' end get '/bar' do 'Hello Bar' end } get '/foo' assert not_found? assert_equal 'pass', response.headers['X-Cascade'] end it "uses optional block passed to pass as route block if no other route is found" do mock_app { get "/" do pass do "this" end "not this" end } get "/" assert ok? assert "this", body end it "passes when matching condition returns false" do mock_app { condition { params[:foo] == 'bar' } get '/:foo' do 'Hello World' end } get '/bar' assert ok? assert_equal 'Hello World', body get '/foo' assert not_found? end it "does not pass when matching condition returns nil" do mock_app { condition { nil } get '/:foo' do 'Hello World' end } get '/bar' assert ok? assert_equal 'Hello World', body end it "passes to next route when condition calls pass explicitly" do mock_app { condition { pass unless params[:foo] == 'bar' } get '/:foo' do 'Hello World' end } get '/bar' assert ok? assert_equal 'Hello World', body get '/foo' assert not_found? end it "passes to the next route when host_name does not match" do mock_app { host_name 'example.com' get '/foo' do 'Hello World' end } get '/foo' assert not_found? get '/foo', {}, { 'HTTP_HOST' => 'example.com' } assert_equal 200, status assert_equal 'Hello World', body end it "passes to the next route when user_agent does not match" do mock_app { user_agent(/Foo/) get '/foo' do 'Hello World' end } get '/foo' assert not_found? get '/foo', {}, { 'HTTP_USER_AGENT' => 'Foo Bar' } assert_equal 200, status assert_equal 'Hello World', body end it "treats missing user agent like an empty string" do mock_app do user_agent(/.*/) get '/' do "Hello World" end end get '/' assert_equal 200, status assert_equal 'Hello World', body end it "makes captures in user agent pattern available in params[:agent]" do mock_app { user_agent(/Foo (.*)/) get '/foo' do 'Hello ' + params[:agent].first end } get '/foo', {}, { 'HTTP_USER_AGENT' => 'Foo Bar' } assert_equal 200, status assert_equal 'Hello Bar', body end it 'matches mime_types with dots, hyphens and plus signs' do mime_types = %w( application/atom+xml application/ecmascript application/EDI-X12 application/EDIFACT application/json application/javascript application/octet-stream application/ogg application/pdf application/postscript application/rdf+xml application/rss+xml application/soap+xml application/font-woff application/xhtml+xml application/xml application/xml-dtd application/xop+xml application/zip application/gzip audio/basic audio/L24 audio/mp4 audio/mpeg audio/ogg audio/vorbis audio/vnd.rn-realaudio audio/vnd.wave audio/webm image/gif image/jpeg image/pjpeg image/png image/svg+xml image/tiff image/vnd.microsoft.icon message/http message/imdn+xml message/partial message/rfc822 model/example model/iges model/mesh model/vrml model/x3d+binary model/x3d+vrml model/x3d+xml multipart/mixed multipart/alternative multipart/related multipart/form-data multipart/signed multipart/encrypted text/cmd text/css text/csv text/html text/javascript application/javascript text/plain text/vcard text/xml video/mpeg video/mp4 video/ogg video/quicktime video/webm video/x-matroska video/x-ms-wmv video/x-flv application/vnd.oasis.opendocument.text application/vnd.oasis.opendocument.spreadsheet application/vnd.oasis.opendocument.presentation application/vnd.oasis.opendocument.graphics application/vnd.ms-excel application/vnd.openxmlformats-officedocument.spreadsheetml.sheet application/vnd.ms-powerpoint application/vnd.openxmlformats-officedocument.presentationml.presentation application/vnd.openxmlformats-officedocument.wordprocessingml.document application/vnd.mozilla.xul+xml application/vnd.google-earth.kml+xml application/x-deb application/x-dvi application/x-font-ttf application/x-javascript application/x-latex application/x-mpegURL application/x-rar-compressed application/x-shockwave-flash application/x-stuffit application/x-tar application/x-www-form-urlencoded application/x-xpinstall audio/x-aac audio/x-caf image/x-xcf text/x-gwt-rpc text/x-jquery-tmpl application/x-pkcs12 application/x-pkcs12 application/x-pkcs7-certificates application/x-pkcs7-certificates application/x-pkcs7-certreqresp application/x-pkcs7-mime application/x-pkcs7-mime application/x-pkcs7-signature ) mime_types.each { |mime_type| assert mime_type.match(Sinatra::Request::HEADER_VALUE_WITH_PARAMS) } end it "filters by accept header" do mock_app { get '/', :provides => :xml do env['HTTP_ACCEPT'] end get '/foo', :provides => :html do env['HTTP_ACCEPT'] end get '/stream', :provides => 'text/event-stream' do env['HTTP_ACCEPT'] end } get '/', {}, { 'HTTP_ACCEPT' => 'application/xml' } assert ok? assert_equal 'application/xml', body assert_equal 'application/xml;charset=utf-8', response.headers['Content-Type'] get '/', {}, {} assert ok? assert_equal '', body assert_equal 'application/xml;charset=utf-8', response.headers['Content-Type'] get '/', {}, { 'HTTP_ACCEPT' => '*/*' } assert ok? assert_equal '*/*', body assert_equal 'application/xml;charset=utf-8', response.headers['Content-Type'] get '/', {}, { 'HTTP_ACCEPT' => 'text/html;q=0.9' } assert !ok? get '/foo', {}, { 'HTTP_ACCEPT' => 'text/html;q=0.9' } assert ok? assert_equal 'text/html;q=0.9', body get '/foo', {}, { 'HTTP_ACCEPT' => '' } assert ok? assert_equal '', body get '/foo', {}, { 'HTTP_ACCEPT' => '*/*' } assert ok? assert_equal '*/*', body get '/foo', {}, { 'HTTP_ACCEPT' => 'application/xml' } assert !ok? get '/stream', {}, { 'HTTP_ACCEPT' => 'text/event-stream' } assert ok? assert_equal 'text/event-stream', body get '/stream', {}, { 'HTTP_ACCEPT' => '' } assert ok? assert_equal '', body get '/stream', {}, { 'HTTP_ACCEPT' => '*/*' } assert ok? assert_equal '*/*', body get '/stream', {}, { 'HTTP_ACCEPT' => 'application/xml' } assert !ok? end it "filters by current Content-Type" do mock_app do before('/txt') { content_type :txt } get('*', :provides => :txt) { 'txt' } before('/html') { content_type :html } get('*', :provides => :html) { 'html' } end get '/', {}, { 'HTTP_ACCEPT' => '*/*' } assert ok? assert_equal 'text/plain;charset=utf-8', response.headers['Content-Type'] assert_body 'txt' get '/txt', {}, { 'HTTP_ACCEPT' => 'text/plain' } assert ok? assert_equal 'text/plain;charset=utf-8', response.headers['Content-Type'] assert_body 'txt' get '/', {}, { 'HTTP_ACCEPT' => 'text/html' } assert ok? assert_equal 'text/html;charset=utf-8', response.headers['Content-Type'] assert_body 'html' end it "allows multiple mime types for accept header" do types = ['image/jpeg', 'image/pjpeg'] mock_app { get '/', :provides => types do env['HTTP_ACCEPT'] end } types.each do |type| get '/', {}, { 'HTTP_ACCEPT' => type } assert ok? assert_equal type, body assert_equal type, response.headers['Content-Type'] end end it 'respects user agent preferences for the content type' do mock_app { get('/', :provides => [:png, :html]) { content_type }} get '/', {}, { 'HTTP_ACCEPT' => 'image/png;q=0.5,text/html;q=0.8' } assert_body 'text/html;charset=utf-8' get '/', {}, { 'HTTP_ACCEPT' => 'image/png;q=0.8,text/html;q=0.5' } assert_body 'image/png' end it 'accepts generic types' do mock_app do get('/', :provides => :xml) { content_type } get('/') { 'no match' } end get '/', {}, { 'HTTP_ACCEPT' => 'foo/*' } assert_body 'no match' get '/', {}, { 'HTTP_ACCEPT' => 'application/*' } assert_body 'application/xml;charset=utf-8' get '/', {}, { 'HTTP_ACCEPT' => '*/*' } assert_body 'application/xml;charset=utf-8' end it 'prefers concrete over partly generic types' do mock_app { get('/', :provides => [:png, :html]) { content_type }} get '/', {}, { 'HTTP_ACCEPT' => 'image/*, text/html' } assert_body 'text/html;charset=utf-8' get '/', {}, { 'HTTP_ACCEPT' => 'image/png, text/*' } assert_body 'image/png' end it 'prefers concrete over fully generic types' do mock_app { get('/', :provides => [:png, :html]) { content_type }} get '/', {}, { 'HTTP_ACCEPT' => '*/*, text/html' } assert_body 'text/html;charset=utf-8' get '/', {}, { 'HTTP_ACCEPT' => 'image/png, */*' } assert_body 'image/png' end it 'prefers partly generic over fully generic types' do mock_app { get('/', :provides => [:png, :html]) { content_type }} get '/', {}, { 'HTTP_ACCEPT' => '*/*, text/*' } assert_body 'text/html;charset=utf-8' get '/', {}, { 'HTTP_ACCEPT' => 'image/*, */*' } assert_body 'image/png' end it 'respects quality with generic types' do mock_app { get('/', :provides => [:png, :html]) { content_type }} get '/', {}, { 'HTTP_ACCEPT' => 'image/*;q=1, text/html;q=0' } assert_body 'image/png' get '/', {}, { 'HTTP_ACCEPT' => 'image/png;q=0.5, text/*;q=0.7' } assert_body 'text/html;charset=utf-8' end it 'supplies a default quality of 1.0' do mock_app { get('/', :provides => [:png, :html]) { content_type }} get '/', {}, { 'HTTP_ACCEPT' => 'image/png;q=0.5, text/*' } assert_body 'text/html;charset=utf-8' end it 'orders types with equal quality by parameter count' do mock_app do get('/', :provides => [:png, :jpg]) { content_type } end lo_png = 'image/png;q=0.5' hi_png = 'image/png;q=0.5;profile=FOGRA40;gamma=0.8' jpeg = 'image/jpeg;q=0.5;compress=0.25' get '/', {}, { 'HTTP_ACCEPT' => "#{lo_png}, #{jpeg}" } assert_body 'image/jpeg' get '/', {}, { 'HTTP_ACCEPT' => "#{hi_png}, #{jpeg}" } assert_body 'image/png' end it 'ignores the quality parameter when ordering by parameter count' do mock_app do get('/', :provides => [:png, :jpg]) { content_type } end lo_png = 'image/png' hi_png = 'image/png;profile=FOGRA40;gamma=0.8' jpeg = 'image/jpeg;q=1.0;compress=0.25' get '/', {}, { 'HTTP_ACCEPT' => "#{jpeg}, #{lo_png}" } assert_body 'image/jpeg' get '/', {}, { 'HTTP_ACCEPT' => "#{jpeg}, #{hi_png}" } assert_body 'image/png' end it 'properly handles quoted strings in parameters' do mock_app do get('/', :provides => [:png, :jpg]) { content_type } end get '/', {}, { 'HTTP_ACCEPT' => 'image/png;q=0.5;profile=",image/jpeg,"' } assert_body 'image/png' get '/', {}, { 'HTTP_ACCEPT' => 'image/png;q=0.5,image/jpeg;q=0;x=";q=1.0"' } assert_body 'image/png' get '/', {}, { 'HTTP_ACCEPT' => 'image/png;q=0.5,image/jpeg;q=0;x="\";q=1.0"' } assert_body 'image/png' end it 'accepts both text/javascript and application/javascript for js' do mock_app { get('/', :provides => :js) { content_type }} get '/', {}, { 'HTTP_ACCEPT' => 'application/javascript' } assert_body 'application/javascript;charset=utf-8' get '/', {}, { 'HTTP_ACCEPT' => 'text/javascript' } assert_body 'text/javascript;charset=utf-8' end it 'accepts both text/xml and application/xml for xml' do mock_app { get('/', :provides => :xml) { content_type }} get '/', {}, { 'HTTP_ACCEPT' => 'application/xml' } assert_body 'application/xml;charset=utf-8' get '/', {}, { 'HTTP_ACCEPT' => 'text/xml' } assert_body 'text/xml;charset=utf-8' end it 'passes a single url param as block parameters when one param is specified' do mock_app { get '/:foo' do |foo| assert_equal 'bar', foo end } get '/bar' assert ok? end it 'passes multiple params as block parameters when many are specified' do mock_app { get '/:foo/:bar/:baz' do |foo, bar, baz| assert_equal 'abc', foo assert_equal 'def', bar assert_equal 'ghi', baz end } get '/abc/def/ghi' assert ok? end it 'passes regular expression captures as block parameters' do mock_app { get(/^\/fo(.*)\/ba(.*)/) do |foo, bar| assert_equal 'orooomma', foo assert_equal 'f', bar 'looks good' end } get '/foorooomma/baf' assert ok? assert_equal 'looks good', body end it "supports mixing multiple splat params like /*/foo/*/* as block parameters" do mock_app { get '/*/foo/*/*' do |foo, bar, baz| assert_equal 'bar', foo assert_equal 'bling', bar assert_equal 'baz/boom', baz 'looks good' end } get '/bar/foo/bling/baz/boom' assert ok? assert_equal 'looks good', body end it 'raises an ArgumentError with block arity > 1 and too many values' do mock_app do get '/:foo/:bar/:baz' do |foo, bar| 'quux' end end assert_raise(ArgumentError) { get '/a/b/c' } end it 'raises an ArgumentError with block param arity > 1 and too few values' do mock_app { get '/:foo/:bar' do |foo, bar, baz| 'quux' end } assert_raise(ArgumentError) { get '/a/b' } end it 'succeeds if no block parameters are specified' do mock_app { get '/:foo/:bar' do 'quux' end } get '/a/b' assert ok? assert_equal 'quux', body end it 'passes all params with block param arity -1 (splat args)' do mock_app { get '/:foo/:bar' do |*args| args.join end } get '/a/b' assert ok? assert_equal 'ab', body end it 'allows custom route-conditions to be set via route options' do protector = Module.new { def protect(*args) condition { unless authorize(params["user"], params["password"]) halt 403, "go away" end } end } mock_app { register protector helpers do def authorize(username, password) username == "foo" && password == "bar" end end get "/", :protect => true do "hey" end } get "/" assert forbidden? assert_equal "go away", body get "/", :user => "foo", :password => "bar" assert ok? assert_equal "hey", body end # NOTE Block params behaves differently under 1.8 and 1.9. Under 1.8, block # param arity is lax: declaring a mismatched number of block params results # in a warning. Under 1.9, block param arity is strict: mismatched block # arity raises an ArgumentError. if RUBY_VERSION >= '1.9' it 'raises an ArgumentError with block param arity 1 and no values' do mock_app { get '/foo' do |foo| 'quux' end } assert_raise(ArgumentError) { get '/foo' } end it 'raises an ArgumentError with block param arity 1 and too many values' do mock_app { get '/:foo/:bar/:baz' do |foo| 'quux' end } assert_raise(ArgumentError) { get '/a/b/c' } end else it 'does not raise an ArgumentError with block param arity 1 and no values' do mock_app { get '/foo' do |foo| 'quux' end } silence_warnings { get '/foo' } assert ok? assert_equal 'quux', body end it 'does not raise an ArgumentError with block param arity 1 and too many values' do mock_app { get '/:foo/:bar/:baz' do |foo| 'quux' end } silence_warnings { get '/a/b/c' } assert ok? assert_equal 'quux', body end end it "matches routes defined in superclasses" do base = Class.new(Sinatra::Base) base.get('/foo') { 'foo in baseclass' } mock_app(base) { get('/bar') { 'bar in subclass' } } get '/foo' assert ok? assert_equal 'foo in baseclass', body get '/bar' assert ok? assert_equal 'bar in subclass', body end it "matches routes in subclasses before superclasses" do base = Class.new(Sinatra::Base) base.get('/foo') { 'foo in baseclass' } base.get('/bar') { 'bar in baseclass' } mock_app(base) { get('/foo') { 'foo in subclass' } } get '/foo' assert ok? assert_equal 'foo in subclass', body get '/bar' assert ok? assert_equal 'bar in baseclass', body end it "adds hostname condition when it is in options" do mock_app { get '/foo', :host => 'host' do 'foo' end } get '/foo' assert not_found? end it 'allows using call to fire another request internally' do mock_app do get '/foo' do status, headers, body = call env.merge("PATH_INFO" => '/bar') [status, headers, body.each.map(&:upcase)] end get '/bar' do "bar" end end get '/foo' assert ok? assert_body "BAR" end it 'plays well with other routing middleware' do middleware = Sinatra.new inner_app = Sinatra.new { get('/foo') { 'hello' } } builder = Rack::Builder.new do use middleware map('/test') { run inner_app } end @app = builder.to_app get '/test/foo' 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 it "sets env['sinatra.route'] to the matched route" do mock_app do after do assert_equal 'GET /users/:id/status', env['sinatra.route'] end get('/users/:id/status') { 'ok' } end get '/users/1/status' end end sinatra-1.4.3/test/result_test.rb0000644000004100000410000000334712161612727017105 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) class ResultTest < Test::Unit::TestCase it "sets response.body when result is a String" do mock_app { get('/') { 'Hello World' } } get '/' assert ok? assert_equal 'Hello World', body end it "sets response.body when result is an Array of Strings" do mock_app { get('/') { ['Hello', 'World'] } } get '/' assert ok? assert_equal 'HelloWorld', body end it "sets response.body when result responds to #each" do mock_app do get('/') do res = lambda { 'Hello World' } def res.each ; yield call ; end return res end end get '/' assert ok? assert_equal 'Hello World', body end it "sets response.body to [] when result is nil" do mock_app { get( '/') { nil } } get '/' assert ok? assert_equal '', body end it "sets status, headers, and body when result is a Rack response tuple" do mock_app { get('/') { [203, {'Content-Type' => 'foo/bar'}, 'Hello World'] } } get '/' assert_equal 203, status assert_equal 'foo/bar', response['Content-Type'] assert_equal 'Hello World', body end it "sets status and body when result is a two-tuple" do mock_app { get('/') { [409, 'formula of'] } } get '/' assert_equal 409, status assert_equal 'formula of', body end it "raises a ArgumentError when result is a non two or three tuple Array" do mock_app { get('/') { [409, 'formula of', 'something else', 'even more'] } } assert_raise(ArgumentError) { get '/' } end it "sets status when result is a Fixnum status code" do mock_app { get('/') { 205 } } get '/' assert_equal 205, status assert_equal '', body end end sinatra-1.4.3/test/slim_test.rb0000644000004100000410000000517012161612727016527 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) begin require 'slim' class SlimTest < Test::Unit::TestCase def slim_app(&block) mock_app do set :views, File.dirname(__FILE__) + '/views' get('/', &block) end get '/' end it 'renders inline slim strings' do slim_app { slim "h1 Hiya\n" } assert ok? assert_equal "

Hiya

", body end it 'renders .slim files in views path' do slim_app { slim :hello } assert ok? assert_equal "

Hello From Slim

", body end it "renders with inline layouts" do mock_app do layout { %(h1\n | THIS. IS. \n == yield.upcase ) } get('/') { slim 'em Sparta' } end get '/' assert ok? assert_equal "

THIS. IS. SPARTA

", body end it "renders with file layouts" do slim_app { slim('| Hello World', :layout => :layout2) } assert ok? assert_equal "

Slim Layout!

Hello World

", body end it "raises error if template not found" do mock_app { get('/') { slim(:no_such_template) } } assert_raise(Errno::ENOENT) { get('/') } end HTML4_DOCTYPE = "" it "passes slim options to the slim engine" do mock_app { get('/') { slim("x foo='bar'", :attr_wrapper => "'") }} get '/' assert ok? assert_body "" end it "passes default slim options to the slim engine" do mock_app do set :slim, :attr_wrapper => "'" get('/') { slim("x foo='bar'") } end get '/' assert ok? assert_body "" end it "merges the default slim options with the overrides and passes them to the slim engine" do mock_app do set :slim, :attr_wrapper => "'" get('/') { slim("x foo='bar'") } get('/other') { slim("x foo='bar'", :attr_wrapper => '"') } end get '/' assert ok? assert_body "" get '/other' assert ok? assert_body '' end it "can render truly nested layouts by accepting a layout and a block with the contents" do mock_app do template(:main_outer_layout) { "h1 Title\n== yield" } template(:an_inner_layout) { "h2 Subtitle\n== yield" } template(:a_page) { "p Contents." } get('/') do slim :main_outer_layout, :layout => false do slim :an_inner_layout do slim :a_page end end end end get '/' assert ok? assert_body "

Title

\n

Subtitle

\n

Contents.

\n" end end rescue LoadError warn "#{$!.to_s}: skipping slim tests" end sinatra-1.4.3/test/rabl_test.rb0000644000004100000410000000371412161612727016505 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) begin require 'rabl' require 'ostruct' require 'json' require 'active_support/core_ext/hash/conversions' class RablTest < Test::Unit::TestCase def rabl_app(&block) mock_app { set :views, File.dirname(__FILE__) + '/views' get '/', &block } get '/' end it 'renders inline rabl strings' do rabl_app do @foo = OpenStruct.new(:baz => 'w00t') rabl %q{ object @foo attributes :baz } end assert ok? assert_equal '{"openstruct":{"baz":"w00t"}}', body end it 'renders .rabl files in views path' do rabl_app do @foo = OpenStruct.new(:bar => 'baz') rabl :hello end assert ok? assert_equal '{"openstruct":{"bar":"baz"}}', body end it "renders with file layouts" do rabl_app { @foo = OpenStruct.new(:bar => 'baz') rabl :hello, :layout => :layout2 } assert ok? assert_equal '{"qux":{"openstruct":{"bar":"baz"}}}', body end it "raises error if template not found" do mock_app { get('/') { rabl :no_such_template } } assert_raise(Errno::ENOENT) { get('/') } end it "passes rabl options to the rabl engine" do mock_app do get('/') do @foo = OpenStruct.new(:bar => 'baz') rabl %q{ object @foo attributes :bar }, :format => 'xml' end end get '/' assert ok? assert_body 'baz' end it "passes default rabl options to the rabl engine" do mock_app do set :rabl, :format => 'xml' get('/') do @foo = OpenStruct.new(:bar => 'baz') rabl %q{ object @foo attributes :bar } end end get '/' assert ok? assert_body 'baz' end end rescue LoadError warn "#{$!.to_s}: skipping rabl tests" end sinatra-1.4.3/test/rack_test.rb0000644000004100000410000000204712161612727016503 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) require 'rack' class RackTest < Test::Unit::TestCase setup do @foo = Sinatra.new { get('/foo') { 'foo' }} @bar = Sinatra.new { get('/bar') { 'bar' }} end def build(*middleware) endpoint = middleware.pop @app = Rack::Builder.app do middleware.each { |m| use m } run endpoint end end def check(*middleware) build(*middleware) assert get('/foo').ok? assert_body 'foo' assert get('/bar').ok? assert_body 'bar' end it 'works as middleware in front of Rack::Lock, with lock enabled' do @foo.enable :lock check(@foo, Rack::Lock, @bar) end it 'works as middleware behind Rack::Lock, with lock enabled' do @foo.enable :lock check(Rack::Lock, @foo, @bar) end it 'works as middleware in front of Rack::Lock, with lock disabled' do @foo.disable :lock check(@foo, Rack::Lock, @bar) end it 'works as middleware behind Rack::Lock, with lock disabled' do @foo.disable :lock check(Rack::Lock, @foo, @bar) end end sinatra-1.4.3/test/stylus_test.rb0000644000004100000410000000441412161612727017126 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) begin require 'stylus' require 'stylus/tilt' begin Stylus.compile '1' rescue RuntimeError raise LoadError, 'unable to find Stylus compiler' end class StylusTest < Test::Unit::TestCase def stylus_app(options = {}, &block) mock_app do set :views, File.dirname(__FILE__) + '/views' set(options) get('/', &block) end get '/' end it 'renders inline Stylus strings' do stylus_app { stylus "a\n margin auto\n" } assert ok? assert body.include?("a {\n margin: auto;\n}\n") end it 'defaults content type to css' do stylus_app { stylus :hello } assert ok? assert_equal "text/css;charset=utf-8", response['Content-Type'] end it 'defaults allows setting content type per route' do stylus_app do content_type :html stylus :hello end assert ok? assert_equal "text/html;charset=utf-8", response['Content-Type'] end it 'defaults allows setting content type globally' do stylus_app(:styl => { :content_type => 'html' }) do stylus :hello end assert ok? assert_equal "text/html;charset=utf-8", response['Content-Type'] end it 'renders .styl files in views path' do stylus_app { stylus :hello } assert ok? assert_include body, "a {\n margin: auto;\n}\n" end it 'ignores the layout option' do stylus_app { stylus :hello, :layout => :layout2 } assert ok? assert_include body, "a {\n margin: auto;\n}\n" end it "raises error if template not found" do mock_app { get('/') { stylus :no_such_template } } assert_raise(Errno::ENOENT) { get('/') } end it "passes stylus options to the stylus engine" do stylus_app { stylus :hello, :no_wrap => true } assert ok? assert_body "a {\n margin: auto;\n}\n" end it "passes default stylus options to the stylus engine" do mock_app do set :stylus, :no_wrap => true # default stylus style is :nested get('/') { stylus :hello } end get '/' assert ok? assert_body "a {\n margin: auto;\n}\n" end end rescue LoadError warn "#{$!.to_s}: skipping stylus tests" end sinatra-1.4.3/test/streaming_test.rb0000644000004100000410000000702612161612727017556 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) class StreamingTest < Test::Unit::TestCase Stream = Sinatra::Helpers::Stream it 'returns the concatenated body' do mock_app do get('/') do stream do |out| out << "Hello" << " " out << "World!" end end end get('/') assert_body "Hello World!" end it 'always yields strings' do stream = Stream.new { |out| out << :foo } stream.each { |str| assert_equal 'foo', str } end it 'postpones body generation' do step = 0 stream = Stream.new do |out| 10.times do out << step step += 1 end end stream.each do |s| assert_equal s, step.to_s step += 1 end end it 'calls the callback after it is done' do step = 0 final = 0 stream = Stream.new { |_| 10.times { step += 1 }} stream.callback { final = step } stream.each {|_|} assert_equal 10, final end it 'does not trigger the callback if close is set to :keep_open' do step = 0 final = 0 stream = Stream.new(Stream, :keep_open) { |_| 10.times { step += 1 } } stream.callback { final = step } stream.each {|_|} assert_equal 0, final end it 'allows adding more than one callback' do a = b = false stream = Stream.new { } stream.callback { a = true } stream.callback { b = true } stream.each {|_| } assert a, 'should trigger first callback' assert b, 'should trigger second callback' end class MockScheduler def initialize(*) @schedule, @defer = [], [] end def schedule(&block) @schedule << block end def defer(&block) @defer << block end def schedule!(*) @schedule.pop.call until @schedule.empty? end def defer!(*) @defer.pop.call until @defer.empty? end end it 'allows dropping in another scheduler' do scheduler = MockScheduler.new processing = sending = done = false stream = Stream.new(scheduler) do |out| processing = true out << :foo end stream.each { sending = true} stream.callback { done = true } scheduler.schedule! assert !processing assert !sending assert !done scheduler.defer! assert processing assert !sending assert !done scheduler.schedule! assert sending assert done end it 'schedules exceptions to be raised on the main thread/event loop/...' do scheduler = MockScheduler.new Stream.new(scheduler) { fail 'should be caught' }.each { } scheduler.defer! assert_raise(RuntimeError) { scheduler.schedule! } end it 'does not trigger an infinite loop if you call close in a callback' do stream = Stream.new { |out| out.callback { out.close }} stream.each { |_| } end it 'gives access to route specific params' do mock_app do get('/:name') do stream { |o| o << params[:name] } end end get '/foo' assert_body 'foo' end it 'sets up async.close if available' do ran = false mock_app do get('/') do close = Object.new def close.callback; yield end def close.errback; end env['async.close'] = close stream(:keep_open) do |out| out.callback { ran = true } end end end get '/' assert ran end it 'has a public interface to inspect its open/closed state' do stream = Stream.new(Stream) { |out| out << :foo } assert !stream.closed? stream.close assert stream.closed? end end sinatra-1.4.3/test/integration/0000755000004100000410000000000012161612727016517 5ustar www-datawww-datasinatra-1.4.3/test/integration/app.rb0000644000004100000410000000164112161612727017626 0ustar www-datawww-data$stderr.puts "loading" require 'sinatra' configure do set :foo, :bar end get '/app_file' do content_type :txt settings.app_file end get '/ping' do 'pong' end get '/stream' do stream do |out| sleep 0.1 out << "a" sleep 1.2 out << "b" end end get '/mainonly' do object = Object.new begin object.send(:get, '/foo') { } 'false' rescue NameError 'true' end end set :out, nil get '/async' do stream(:keep_open) { |o| (settings.out = o) << "hi!" } end get '/send' do settings.out << params[:msg] if params[:msg] settings.out.close if params[:close] "ok" end class Subclass < Sinatra::Base set :out, nil get '/subclass/async' do stream(:keep_open) { |o| (settings.out = o) << "hi!" } end get '/subclass/send' do settings.out << params[:msg] if params[:msg] settings.out.close if params[:close] "ok" end end use Subclass $stderr.puts "starting" sinatra-1.4.3/test/compile_test.rb0000644000004100000410000002012012161612727017203 0ustar www-datawww-data# I like coding: UTF-8 require File.expand_path('../helper', __FILE__) class CompileTest < Test::Unit::TestCase def self.converts pattern, expected_regexp it "generates #{expected_regexp.source} from #{pattern}" do compiled, _ = compiled pattern assert_equal expected_regexp, compiled, "Pattern #{pattern} is not compiled into #{expected_regexp.source}. Was #{compiled.source}." end end def self.parses pattern, example, expected_params it "parses #{example} with #{pattern} into params #{expected_params}" do compiled, keys = compiled pattern match = compiled.match(example) fail %Q{"#{example}" does not parse on pattern "#{pattern}" (compiled pattern is #{compiled.source}).} unless match # Aggregate e.g. multiple splat values into one array. # params = keys.zip(match.captures).reduce({}) do |hash, mapping| key, value = mapping hash[key] = if existing = hash[key] existing.respond_to?(:to_ary) ? existing << value : [existing, value] else value end hash end assert_equal expected_params, params, "Pattern #{pattern} does not match path #{example}." end end def self.fails pattern, example it "does not parse #{example} with #{pattern}" do compiled, _ = compiled pattern match = compiled.match(example) fail %Q{"#{pattern}" does parse "#{example}" but it should fail} if match end end def compiled pattern app ||= mock_app {} compiled, keys = app.send(:compile, pattern) [compiled, keys] end converts "/", %r{\A/\z} parses "/", "/", {} converts "/foo", %r{\A/foo\z} parses "/foo", "/foo", {} converts "/:foo", %r{\A/([^/?#]+)\z} parses "/:foo", "/foo", "foo" => "foo" parses "/:foo", "/foo.bar", "foo" => "foo.bar" parses "/:foo", "/foo%2Fbar", "foo" => "foo%2Fbar" parses "/:foo", "/%0Afoo", "foo" => "%0Afoo" fails "/:foo", "/foo?" fails "/:foo", "/foo/bar" fails "/:foo", "/" fails "/:foo", "/foo/" converts "/föö", %r{\A/f%[Cc]3%[Bb]6%[Cc]3%[Bb]6\z} parses "/föö", "/f%C3%B6%C3%B6", {} converts "/:foo/:bar", %r{\A/([^/?#]+)/([^/?#]+)\z} parses "/:foo/:bar", "/foo/bar", "foo" => "foo", "bar" => "bar" converts "/hello/:person", %r{\A/hello/([^/?#]+)\z} parses "/hello/:person", "/hello/Frank", "person" => "Frank" converts "/?:foo?/?:bar?", %r{\A/?([^/?#]+)?/?([^/?#]+)?\z} parses "/?:foo?/?:bar?", "/hello/world", "foo" => "hello", "bar" => "world" parses "/?:foo?/?:bar?", "/hello", "foo" => "hello", "bar" => nil parses "/?:foo?/?:bar?", "/", "foo" => nil, "bar" => nil parses "/?:foo?/?:bar?", "", "foo" => nil, "bar" => nil converts "/*", %r{\A/(.*?)\z} parses "/*", "/", "splat" => "" parses "/*", "/foo", "splat" => "foo" parses "/*", "/foo/bar", "splat" => "foo/bar" converts "/:foo/*", %r{\A/([^/?#]+)/(.*?)\z} parses "/:foo/*", "/foo/bar/baz", "foo" => "foo", "splat" => "bar/baz" converts "/:foo/:bar", %r{\A/([^/?#]+)/([^/?#]+)\z} parses "/:foo/:bar", "/user@example.com/name", "foo" => "user@example.com", "bar" => "name" converts "/test$/", %r{\A/test(?:\$|%24)/\z} parses "/test$/", "/test$/", {} converts "/te+st/", %r{\A/te(?:\+|%2[Bb])st/\z} parses "/te+st/", "/te+st/", {} fails "/te+st/", "/test/" fails "/te+st/", "/teeest/" converts "/test(bar)/", %r{\A/test(?:\(|%28)bar(?:\)|%29)/\z} parses "/test(bar)/", "/test(bar)/", {} converts "/path with spaces", %r{\A/path(?:%20|(?:\+|%2[Bb]))with(?:%20|(?:\+|%2[Bb]))spaces\z} parses "/path with spaces", "/path%20with%20spaces", {} parses "/path with spaces", "/path%2Bwith%2Bspaces", {} parses "/path with spaces", "/path+with+spaces", {} converts "/foo&bar", %r{\A/foo(?:&|%26)bar\z} parses "/foo&bar", "/foo&bar", {} converts "/:foo/*", %r{\A/([^/?#]+)/(.*?)\z} parses "/:foo/*", "/hello%20world/how%20are%20you", "foo" => "hello%20world", "splat" => "how%20are%20you" converts "/*/foo/*/*", %r{\A/(.*?)/foo/(.*?)/(.*?)\z} parses "/*/foo/*/*", "/bar/foo/bling/baz/boom", "splat" => ["bar", "bling", "baz/boom"] fails "/*/foo/*/*", "/bar/foo/baz" converts "/test.bar", %r{\A/test(?:\.|%2[Ee])bar\z} parses "/test.bar", "/test.bar", {} fails "/test.bar", "/test0bar" converts "/:file.:ext", %r{\A/((?:[^\./?#%]|(?:%[^2].|%[2][^Ee]))+)(?:\.|%2[Ee])((?:[^/?#%]|(?:%[^2].|%[2][^Ee]))+)\z} parses "/:file.:ext", "/pony.jpg", "file" => "pony", "ext" => "jpg" parses "/:file.:ext", "/pony%2Ejpg", "file" => "pony", "ext" => "jpg" fails "/:file.:ext", "/.jpg" converts "/:name.?:format?", %r{\A/((?:[^\./?#%]|(?:%[^2].|%[2][^Ee]))+)(?:\.|%2[Ee])?((?:[^/?#%]|(?:%[^2].|%[2][^Ee]))+)?\z} parses "/:name.?:format?", "/foo", "name" => "foo", "format" => nil parses "/:name.?:format?", "/foo.bar", "name" => "foo", "format" => "bar" parses "/:name.?:format?", "/foo%2Ebar", "name" => "foo", "format" => "bar" parses "/:name?.?:format", "/.bar", "name" => nil, "format" => "bar" parses "/:name?.?:format?", "/.bar", "name" => nil, "format" => "bar" parses "/:name?.:format?", "/.bar", "name" => nil, "format" => "bar" fails "/:name.:format", "/.bar" fails "/:name.?:format?", "/.bar" converts "/:user@?:host?", %r{\A/((?:[^@/?#%]|(?:%[^4].|%[4][^0]))+)(?:@|%40)?((?:[^@/?#%]|(?:%[^4].|%[4][^0]))+)?\z} parses "/:user@?:host?", "/foo@bar", "user" => "foo", "host" => "bar" parses "/:user@?:host?", "/foo.foo@bar", "user" => "foo.foo", "host" => "bar" parses "/:user@?:host?", "/foo@bar.bar", "user" => "foo", "host" => "bar.bar" # From https://gist.github.com/2154980#gistcomment-169469. # # converts "/:name(.:format)?", %r{\A/([^\.%2E/?#]+)(?:\(|%28)(?:\.|%2E)([^\.%2E/?#]+)(?:\)|%29)?\z} # parses "/:name(.:format)?", "/foo", "name" => "foo", "format" => nil # parses "/:name(.:format)?", "/foo.bar", "name" => "foo", "format" => "bar" fails "/:name(.:format)?", "/foo." parses "/:id/test.bar", "/3/test.bar", {"id" => "3"} parses "/:id/test.bar", "/2/test.bar", {"id" => "2"} parses "/:id/test.bar", "/2E/test.bar", {"id" => "2E"} parses "/:id/test.bar", "/2e/test.bar", {"id" => "2e"} parses "/:id/test.bar", "/%2E/test.bar", {"id" => "%2E"} parses '/10/:id', '/10/test', "id" => "test" parses '/10/:id', '/10/te.st', "id" => "te.st" parses '/10.1/:id', '/10.1/test', "id" => "test" parses '/10.1/:id', '/10.1/te.st', "id" => "te.st" parses '/:foo/:id', '/10.1/te.st', "foo" => "10.1", "id" => "te.st" parses '/:foo/:id', '/10.1.2/te.st', "foo" => "10.1.2", "id" => "te.st" parses '/:foo.:bar/:id', '/10.1/te.st', "foo" => "10", "bar" => "1", "id" => "te.st" fails '/:foo.:bar/:id', '/10.1.2/te.st' # We don't do crazy. parses '/:a/:b.?:c?', '/a/b', "a" => "a", "b" => "b", "c" => nil parses '/:a/:b.?:c?', '/a/b.c', "a" => "a", "b" => "b", "c" => "c" parses '/:a/:b.?:c?', '/a.b/c', "a" => "a.b", "b" => "c", "c" => nil parses '/:a/:b.?:c?', '/a.b/c.d', "a" => "a.b", "b" => "c", "c" => "d" fails '/:a/:b.?:c?', '/a.b/c.d/e' parses "/:file.:ext", "/pony%2ejpg", "file" => "pony", "ext" => "jpg" parses "/:file.:ext", "/pony%E6%AD%A3%2Ejpg", "file" => "pony%E6%AD%A3", "ext" => "jpg" parses "/:file.:ext", "/pony%e6%ad%a3%2ejpg", "file" => "pony%e6%ad%a3", "ext" => "jpg" parses "/:file.:ext", "/pony正%2Ejpg", "file" => "pony正", "ext" => "jpg" parses "/:file.:ext", "/pony正%2ejpg", "file" => "pony正", "ext" => "jpg" parses "/:file.:ext", "/pony正..jpg", "file" => "pony正", "ext" => ".jpg" fails "/:file.:ext", "/pony正.%2ejpg" converts "/:name.:format", %r{\A/((?:[^\./?#%]|(?:%[^2].|%[2][^Ee]))+)(?:\.|%2[Ee])((?:[^/?#%]|(?:%[^2].|%[2][^Ee]))+)\z} parses "/:name.:format", "/file.tar.gz", "name" => "file", "format" => "tar.gz" parses "/:name.:format1.:format2", "/file.tar.gz", "name" => "file", "format1" => "tar", "format2" => "gz" parses "/:name.:format1.:format2", "/file.temp.tar.gz", "name" => "file", "format1" => "temp", "format2" => "tar.gz" # From issue #688. # parses "/articles/10.1103/:doi", "/articles/10.1103/PhysRevLett.110.026401", "doi" => "PhysRevLett.110.026401" end sinatra-1.4.3/test/builder_test.rb0000644000004100000410000000431212161612727017206 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) begin require 'builder' class BuilderTest < Test::Unit::TestCase def builder_app(options = {}, &block) mock_app do set :views, File.dirname(__FILE__) + '/views' set options get('/', &block) end get '/' end it 'renders inline Builder strings' do builder_app { builder 'xml.instruct!' } assert ok? assert_equal %{\n}, body end it 'defaults content type to xml' do builder_app { builder 'xml.instruct!' } assert ok? assert_equal "application/xml;charset=utf-8", response['Content-Type'] end it 'defaults allows setting content type per route' do builder_app do content_type :html builder 'xml.instruct!' end assert ok? assert_equal "text/html;charset=utf-8", response['Content-Type'] end it 'defaults allows setting content type globally' do builder_app(:builder => { :content_type => 'html' }) do builder 'xml.instruct!' end assert ok? assert_equal "text/html;charset=utf-8", response['Content-Type'] end it 'renders inline blocks' do builder_app do @name = "Frank & Mary" builder { |xml| xml.couple @name } end assert ok? assert_equal "Frank & Mary\n", body end it 'renders .builder files in views path' do builder_app do @name = "Blue" builder :hello end assert ok? assert_equal %(You're my boy, Blue!\n), body end it "renders with inline layouts" do mock_app do layout { %(xml.layout { xml << yield }) } get('/') { builder %(xml.em 'Hello World') } end get '/' assert ok? assert_equal "\nHello World\n\n", body end it "renders with file layouts" do builder_app do builder %(xml.em 'Hello World'), :layout => :layout2 end assert ok? assert_equal "\nHello World\n\n", body end it "raises error if template not found" do mock_app do get('/') { builder :no_such_template } end assert_raise(Errno::ENOENT) { get('/') } end end rescue LoadError warn "#{$!.to_s}: skipping builder tests" end sinatra-1.4.3/test/views/0000755000004100000410000000000012161612727015331 5ustar www-datawww-datasinatra-1.4.3/test/views/hello.str0000644000004100000410000000003212161612727017161 0ustar www-datawww-data

Hello From String

sinatra-1.4.3/test/views/layout2.liquid0000644000004100000410000000005312161612727020137 0ustar www-datawww-data

Liquid Layout!

{{ yield }}

sinatra-1.4.3/test/views/layout2.str0000644000004100000410000000004012161612727017454 0ustar www-datawww-data

String Layout!

#{yield}sinatra-1.4.3/test/views/layout2.nokogiri0000644000004100000410000000004112161612727020466 0ustar www-datawww-dataxml.layout do xml << yield end sinatra-1.4.3/test/views/hello.test0000644000004100000410000000001512161612727017331 0ustar www-datawww-dataHello World! sinatra-1.4.3/test/views/hello.coffee0000644000004100000410000000001512161612727017601 0ustar www-datawww-dataalert "Aye!" sinatra-1.4.3/test/views/b/0000755000004100000410000000000012161612727015552 5ustar www-datawww-datasinatra-1.4.3/test/views/b/in_b.str0000644000004100000410000000001312161612727017205 0ustar www-datawww-dataGimme a B! sinatra-1.4.3/test/views/hello.sass0000644000004100000410000000004012161612727017321 0ustar www-datawww-data#sass :background-color white sinatra-1.4.3/test/views/layout2.slim0000644000004100000410000000003512161612727017614 0ustar www-datawww-datah1 Slim Layout! p == yield sinatra-1.4.3/test/views/layout2.mab0000644000004100000410000000004112161612727017404 0ustar www-datawww-datah1 "Markaby Layout!" p { yield } sinatra-1.4.3/test/views/layout2.test0000644000004100000410000000001212161612727017622 0ustar www-datawww-dataLayout 2! sinatra-1.4.3/test/views/utf8.erb0000644000004100000410000000007712161612727016715 0ustar www-datawww-data

<%= value %>

Ingen vill veta var du köpt din tröja. sinatra-1.4.3/test/views/hello.textile0000644000004100000410000000002712161612727020033 0ustar www-datawww-datah1. Hello From Textile sinatra-1.4.3/test/views/hello.styl0000644000004100000410000000002012161612727017341 0ustar www-datawww-dataa margin auto sinatra-1.4.3/test/views/hello.rabl0000644000004100000410000000003412161612727017273 0ustar www-datawww-dataobject @foo attributes :bar sinatra-1.4.3/test/views/hello.rdoc0000644000004100000410000000002212161612727017277 0ustar www-datawww-data= Hello From RDoc sinatra-1.4.3/test/views/calc.html.erb0000644000004100000410000000001412161612727017663 0ustar www-datawww-data<%= 1 + 1 %>sinatra-1.4.3/test/views/hello.creole0000644000004100000410000000002412161612727017623 0ustar www-datawww-data= Hello From Creole sinatra-1.4.3/test/views/a/0000755000004100000410000000000012161612727015551 5ustar www-datawww-datasinatra-1.4.3/test/views/a/in_a.str0000644000004100000410000000001412161612727017204 0ustar www-datawww-dataGimme an A! sinatra-1.4.3/test/views/layout2.wlang0000644000004100000410000000002712161612727017761 0ustar www-datawww-dataWLang Layout! +{yield} sinatra-1.4.3/test/views/error.sass0000644000004100000410000000002612161612727017353 0ustar www-datawww-data#sass +argle-bargle sinatra-1.4.3/test/views/hello.nokogiri0000644000004100000410000000004712161612727020200 0ustar www-datawww-dataxml.exclaim "You're my boy, #{@name}!" sinatra-1.4.3/test/views/layout2.haml0000644000004100000410000000003312161612727017567 0ustar www-datawww-data%h1 HAML Layout! %p= yield sinatra-1.4.3/test/views/ascii.erb0000644000004100000410000000005512161612727017113 0ustar www-datawww-dataThis file has no unicode in it! <%= value %> sinatra-1.4.3/test/views/error.erb0000644000004100000410000000020312161612727017147 0ustar www-datawww-dataHello <%= 'World' %> <% raise 'Goodbye' unless defined?(french) && french %> <% raise 'Au revoir' if defined?(french) && french %> sinatra-1.4.3/test/views/hello.less0000644000004100000410000000010112161612727017314 0ustar www-datawww-data@white_colour: #fff; #main { background-color: @white_colour; }sinatra-1.4.3/test/views/hello.haml0000644000004100000410000000002412161612727017273 0ustar www-datawww-data%h1 Hello From Haml sinatra-1.4.3/test/views/hello.radius0000644000004100000410000000003312161612727017641 0ustar www-datawww-data

Hello From Radius

sinatra-1.4.3/test/views/hello.scss0000644000004100000410000000004312161612727017326 0ustar www-datawww-data#scss { background-color: white }sinatra-1.4.3/test/views/hello.md0000644000004100000410000000002512161612727016753 0ustar www-datawww-data# Hello From Markdownsinatra-1.4.3/test/views/hello.mab0000644000004100000410000000003012161612727017106 0ustar www-datawww-datah1 "Hello From Markaby" sinatra-1.4.3/test/views/error.builder0000644000004100000410000000004312161612727020027 0ustar www-datawww-dataxml.error do raise "goodbye" end sinatra-1.4.3/test/views/hello.wlang0000644000004100000410000000002212161612727017460 0ustar www-datawww-dataHello from wlang! sinatra-1.4.3/test/views/layout2.rabl0000644000004100000410000000005012161612727017565 0ustar www-datawww-datanode(:qux) do ::JSON.parse(yield) end sinatra-1.4.3/test/views/foo/0000755000004100000410000000000012161612727016114 5ustar www-datawww-datasinatra-1.4.3/test/views/foo/hello.test0000644000004100000410000000003512161612727020116 0ustar www-datawww-datafrom another views directory sinatra-1.4.3/test/views/layout2.erb0000644000004100000410000000003112161612727017414 0ustar www-datawww-dataERB Layout! <%= yield %> sinatra-1.4.3/test/views/hello.slim0000644000004100000410000000002312161612727017315 0ustar www-datawww-datah1 Hello From Slim sinatra-1.4.3/test/views/hello.liquid0000644000004100000410000000003312161612727017641 0ustar www-datawww-data

Hello From Liquid

sinatra-1.4.3/test/views/nested.str0000644000004100000410000000005112161612727017341 0ustar www-datawww-data#{render :str, :hello}sinatra-1.4.3/test/views/explicitly_nested.str0000644000004100000410000000007612161612727021616 0ustar www-datawww-data#{render :str, :hello, :layout => :layout2}sinatra-1.4.3/test/views/hello.builder0000644000004100000410000000004712161612727020005 0ustar www-datawww-dataxml.exclaim "You're my boy, #{@name}!" sinatra-1.4.3/test/views/hello.yajl0000644000004100000410000000003312161612727017311 0ustar www-datawww-datajson = { :yajl => "hello" }sinatra-1.4.3/test/views/layout2.builder0000644000004100000410000000004112161612727020273 0ustar www-datawww-dataxml.layout do xml << yield end sinatra-1.4.3/test/views/error.haml0000644000004100000410000000017212161612727017325 0ustar www-datawww-data%h1 Hello From Haml = raise 'goodbye' unless defined?(french) && french = raise 'au revoir' if defined?(french) && french sinatra-1.4.3/test/views/layout2.radius0000644000004100000410000000005312161612727020137 0ustar www-datawww-data

Radius Layout!

sinatra-1.4.3/test/views/hello.erb0000644000004100000410000000002512161612727017123 0ustar www-datawww-dataHello <%= 'World' %> sinatra-1.4.3/test/response_test.rb0000644000004100000410000000355312161612727017424 0ustar www-datawww-data# encoding: utf-8 require File.expand_path('../helper', __FILE__) class ResponseTest < Test::Unit::TestCase setup { @response = Sinatra::Response.new } def assert_same_body(a, b) assert_equal a.to_enum(:each).to_a, b.to_enum(:each).to_a end it "initializes with 200, text/html, and empty body" do assert_equal 200, @response.status assert_equal 'text/html', @response['Content-Type'] assert_equal [], @response.body end it 'uses case insensitive headers' do @response['content-type'] = 'application/foo' assert_equal 'application/foo', @response['Content-Type'] assert_equal 'application/foo', @response['CONTENT-TYPE'] end it 'writes to body' do @response.body = 'Hello' @response.write ' World' assert_equal 'Hello World', @response.body.join end [204, 304].each do |status_code| it "removes the Content-Type header and body when response status is #{status_code}" do @response.status = status_code @response.body = ['Hello World'] assert_equal [status_code, {}, []], @response.finish end end it 'Calculates the Content-Length using the bytesize of the body' do @response.body = ['Hello', 'World!', '✈'] status, headers, body = @response.finish assert_equal '14', headers['Content-Length'] assert_same_body @response.body, body end it 'does not call #to_ary or #inject on the body' do object = Object.new def object.inject(*) fail 'called' end def object.to_ary(*) fail 'called' end def object.each(*) end @response.body = object assert @response.finish end it 'does not nest a Sinatra::Response' do @response.body = Sinatra::Response.new ["foo"] assert_same_body @response.body, ["foo"] end it 'does not nest a Rack::Response' do @response.body = Rack::Response.new ["foo"] assert_same_body @response.body, ["foo"] end end sinatra-1.4.3/test/public/0000755000004100000410000000000012161612727015452 5ustar www-datawww-datasinatra-1.4.3/test/public/favicon.ico0000644000004100000410000000000012161612727017561 0ustar www-datawww-datasinatra-1.4.3/test/textile_test.rb0000644000004100000410000000301012161612727017230 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) begin require 'redcloth' class TextileTest < Test::Unit::TestCase def textile_app(&block) mock_app do set :views, File.dirname(__FILE__) + '/views' get('/', &block) end get '/' end it 'renders inline textile strings' do textile_app { textile('h1. Hiya') } assert ok? assert_equal "

Hiya

", body end it 'renders .textile files in views path' do textile_app { textile(:hello) } assert ok? assert_equal "

Hello From Textile

", body end it "raises error if template not found" do mock_app { get('/') { textile(:no_such_template) } } assert_raise(Errno::ENOENT) { get('/') } end it "renders with inline layouts" do mock_app do layout { 'THIS. IS. #{yield.upcase}!' } get('/') { textile('Sparta', :layout_engine => :str) } end get '/' assert ok? assert_like 'THIS. IS.

SPARTA

!', body end it "renders with file layouts" do textile_app { textile('Hello World', :layout => :layout2, :layout_engine => :erb) } assert ok? assert_body "ERB Layout!\n

Hello World

" end it "can be used in a nested fashion for partials and whatnot" do mock_app do template(:inner) { "hi" } template(:outer) { "<%= textile :inner %>" } get('/') { erb :outer } end get '/' assert ok? assert_like '

hi

', body end end rescue LoadError warn "#{$!.to_s}: skipping textile tests" end sinatra-1.4.3/test/rdoc_test.rb0000644000004100000410000000325712161612727016516 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) begin require 'rdoc' require 'rdoc/markup/to_html' class RdocTest < Test::Unit::TestCase def rdoc_app(&block) mock_app do set :views, File.dirname(__FILE__) + '/views' get('/', &block) end get '/' end it 'renders inline rdoc strings' do rdoc_app { rdoc '= Hiya' } assert ok? assert_body /]*>Hiya(¶<\/a> ↑<\/a><\/span>)?<\/h1>/ end it 'renders .rdoc files in views path' do rdoc_app { rdoc :hello } assert ok? assert_body /]*>Hello From RDoc(¶<\/a> ↑<\/a><\/span>)?<\/h1>/ end it "raises error if template not found" do mock_app { get('/') { rdoc :no_such_template } } assert_raise(Errno::ENOENT) { get('/') } end it "renders with inline layouts" do mock_app do layout { 'THIS. IS. #{yield.upcase}!' } get('/') { rdoc 'Sparta', :layout_engine => :str } end get '/' assert ok? assert_like 'THIS. IS.

SPARTA

!', body end it "renders with file layouts" do rdoc_app { rdoc 'Hello World', :layout => :layout2, :layout_engine => :erb } assert ok? assert_body "ERB Layout!\n

Hello World

" end it "can be used in a nested fashion for partials and whatnot" do mock_app do template(:inner) { "hi" } template(:outer) { "<%= rdoc :inner %>" } get('/') { erb :outer } end get '/' assert ok? assert_like '

hi

', body end end rescue LoadError warn "#{$!.to_s}: skipping rdoc tests" end sinatra-1.4.3/test/sass_test.rb0000644000004100000410000000647512161612727016545 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) begin raise LoadError, 'sass not supported on Ruby 2.0' if RUBY_VERSION >= '2.0' require 'sass' class SassTest < Test::Unit::TestCase def sass_app(options = {}, &block) mock_app do set :views, File.dirname(__FILE__) + '/views' set options get('/', &block) end get '/' end it 'renders inline Sass strings' do sass_app { sass "#sass\n :background-color white\n" } assert ok? assert_equal "#sass {\n background-color: white; }\n", body end it 'defaults content type to css' do sass_app { sass "#sass\n :background-color white\n" } assert ok? assert_equal "text/css;charset=utf-8", response['Content-Type'] end it 'defaults allows setting content type per route' do sass_app do content_type :html sass "#sass\n :background-color white\n" end assert ok? assert_equal "text/html;charset=utf-8", response['Content-Type'] end it 'defaults allows setting content type globally' do sass_app(:sass => { :content_type => 'html' }) { sass "#sass\n :background-color white\n" } assert ok? assert_equal "text/html;charset=utf-8", response['Content-Type'] end it 'renders .sass files in views path' do sass_app { sass :hello } assert ok? assert_equal "#sass {\n background-color: white; }\n", body end it 'ignores the layout option' do sass_app { sass :hello, :layout => :layout2 } assert ok? assert_equal "#sass {\n background-color: white; }\n", body end it "raises error if template not found" do mock_app { get('/') { sass :no_such_template } } assert_raise(Errno::ENOENT) { get('/') } end it "passes SASS options to the Sass engine" do sass_app do sass( "#sass\n :background-color white\n :color black\n", :style => :compact ) end assert ok? assert_equal("#sass { background-color: white; color: black; }\n", body) end it "passes default SASS options to the Sass engine" do mock_app do set :sass, {:style => :compact} # default Sass style is :nested get('/') { sass("#sass\n :background-color white\n :color black\n") } end get '/' assert ok? assert_equal "#sass { background-color: white; color: black; }\n", body end it "merges the default SASS options with the overrides" do mock_app do # default Sass attribute_syntax is :normal (with : in front) set :sass, {:style => :compact, :attribute_syntax => :alternate } get('/') { sass("#sass\n background-color: white\n color: black\n") } get('/raised') do # retains global attribute_syntax settings sass( "#sass\n :background-color white\n :color black\n", :style => :expanded ) end get('/expanded_normal') do sass( "#sass\n :background-color white\n :color black\n", :style => :expanded, :attribute_syntax => :normal ) end end get '/' assert ok? assert_equal "#sass { background-color: white; color: black; }\n", body assert_raise(Sass::SyntaxError) { get('/raised') } get '/expanded_normal' assert ok? assert_equal "#sass {\n background-color: white;\n color: black;\n}\n", body end end rescue LoadError warn "#{$!.to_s}: skipping sass tests" end sinatra-1.4.3/test/readme_test.rb0000644000004100000410000000527312161612727017024 0ustar www-datawww-data# Tests to check if all the README examples work. require File.expand_path('../helper', __FILE__) class ReadmeTest < Test::Unit::TestCase example do mock_app { get('/') { 'Hello world!' } } get '/' assert_body 'Hello world!' end section "Routes" do example do mock_app do get('/') { ".. show something .." } post('/') { ".. create something .." } put('/') { ".. replace something .." } patch('/') { ".. modify something .." } delete('/') { ".. annihilate something .." } options('/') { ".. appease something .." } link('/') { ".. affiliate something .." } unlink('/') { ".. separate something .." } end get '/' assert_body '.. show something ..' post '/' assert_body '.. create something ..' put '/' assert_body '.. replace something ..' patch '/' assert_body '.. modify something ..' delete '/' assert_body '.. annihilate something ..' options '/' assert_body '.. appease something ..' link '/' assert_body '.. affiliate something ..' unlink '/' assert_body '.. separate something ..' end example do mock_app do get('/hello/:name') do # matches "GET /hello/foo" and "GET /hello/bar" # params[:name] is 'foo' or 'bar' "Hello #{params[:name]}!" end end get '/hello/foo' assert_body 'Hello foo!' get '/hello/bar' assert_body 'Hello bar!' end example do mock_app { get('/hello/:name') { |n| "Hello #{n}!" } } get '/hello/foo' assert_body 'Hello foo!' get '/hello/bar' assert_body 'Hello bar!' end example do mock_app do get('/say/*/to/*') do # matches /say/hello/to/world params[:splat].inspect # => ["hello", "world"] end get('/download/*.*') do # matches /download/path/to/file.xml params[:splat].inspect # => ["path/to/file", "xml"] end end get "/say/hello/to/world" assert_body '["hello", "world"]' get "/download/path/to/file.xml" assert_body '["path/to/file", "xml"]' end example do mock_app do get(%r{/hello/([\w]+)}) { "Hello, #{params[:captures].first}!" } end get '/hello/foo' assert_body 'Hello, foo!' get '/hello/bar' assert_body 'Hello, bar!' end example do mock_app do get( %r{/hello/([\w]+)}) { |c| "Hello, #{c}!" } end get '/hello/foo' assert_body 'Hello, foo!' get '/hello/bar' assert_body 'Hello, bar!' end end end sinatra-1.4.3/test/templates_test.rb0000644000004100000410000002345212161612727017564 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path('../helper', __FILE__) File.delete(File.dirname(__FILE__) + '/views/layout.test') rescue nil class TestTemplate < Tilt::Template def prepare end def evaluate(scope, locals={}, &block) inner = block ? block.call : '' data + inner end Tilt.register 'test', self end class TemplatesTest < Test::Unit::TestCase def render_app(base=Sinatra::Base, options = {}, &block) base, options = Sinatra::Base, base if base.is_a? Hash mock_app(base) do set :views, File.dirname(__FILE__) + '/views' set options get('/', &block) template(:layout3) { "Layout 3!\n" } end get '/' end def with_default_layout layout = File.dirname(__FILE__) + '/views/layout.test' File.open(layout, 'wb') { |io| io.write "Layout!\n" } yield ensure File.unlink(layout) rescue nil end it 'falls back to engine layout' do mock_app do template(:layout3) { 'Layout 3!<%= yield %>' } set :erb, :layout => :layout3 get('/') do erb('Hello World!', { :layout => true }) end end get '/' assert ok? assert_equal "Layout 3!Hello World!", body end it 'falls back to default layout if engine layout is true' do mock_app do template(:layout) { 'Layout!!! <%= yield %>' } set :erb, :layout => true get('/') do erb('Hello World!', { :layout => true }) end end get '/' assert ok? assert_equal "Layout!!! Hello World!", body end it 'renders no layout if layout if falsy' do mock_app do template(:layout) { 'Layout!!! <%= yield %>' } set :erb, :layout => true get('/') do erb('Hello World!', { :layout => nil }) end end get '/' assert ok? assert_equal "Hello World!", body end it 'renders String templates directly' do render_app { render(:test, 'Hello World') } assert ok? assert_equal 'Hello World', body end it 'renders Proc templates using the call result' do render_app { render(:test, Proc.new {'Hello World'}) } assert ok? assert_equal 'Hello World', body end it 'looks up Symbol templates in views directory' do render_app { render(:test, :hello) } assert ok? assert_equal "Hello World!\n", body end it 'uses the default layout template if not explicitly overridden' do with_default_layout do render_app { render(:test, :hello) } assert ok? assert_equal "Layout!\nHello World!\n", body end end it 'uses the default layout template if not really overriden' do with_default_layout do render_app { render(:test, :hello, :layout => true) } assert ok? assert_equal "Layout!\nHello World!\n", body end end it 'uses the layout template specified' do render_app { render(:test, :hello, :layout => :layout2) } assert ok? assert_equal "Layout 2!\nHello World!\n", body end it 'uses layout templates defined with the #template method' do render_app { render(:test, :hello, :layout => :layout3) } assert ok? assert_equal "Layout 3!\nHello World!\n", body end it 'avoids wrapping layouts around nested templates' do render_app { render(:str, :nested, :layout => :layout2) } assert ok? assert_equal( "

String Layout!

\n

Hello From String

", body ) end it 'allows explicitly wrapping layouts around nested templates' do render_app { render(:str, :explicitly_nested, :layout => :layout2) } assert ok? assert_equal( "

String Layout!

\n

String Layout!

\n

Hello From String

", body ) end it 'two independent render calls do not disable layouts' do render_app do render :str, :explicitly_nested, :layout => :layout2 render :str, :nested, :layout => :layout2 end assert ok? assert_equal( "

String Layout!

\n

Hello From String

", body ) end it 'is possible to use partials in layouts' do render_app do settings.layout { "<%= erb 'foo' %><%= yield %>" } erb 'bar' end assert ok? assert_equal "foobar", body end it 'loads templates from source file' do mock_app { enable(:inline_templates) } assert_equal "this is foo\n\n", @app.templates[:foo][0] assert_equal "X\n= yield\nX\n", @app.templates[:layout][0] end it 'ignores spaces after names of inline templates' do mock_app { enable(:inline_templates) } assert_equal "There's a space after 'bar'!\n\n", @app.templates[:bar][0] assert_equal "this is not foo\n\n", @app.templates[:"foo bar"][0] end it 'loads templates from given source file' do mock_app { set(:inline_templates, __FILE__) } assert_equal "this is foo\n\n", @app.templates[:foo][0] end test 'inline_templates ignores IO errors' do assert_nothing_raised { mock_app { set(:inline_templates, '/foo/bar') } } assert @app.templates.empty? end it 'allows unicode in inline templates' do mock_app { set(:inline_templates, __FILE__) } assert_equal( "Den som tror at hemma det är där man bor har aldrig vart hos mig.\n\n", @app.templates[:umlaut][0] ) end it 'loads templates from specified views directory' do render_app { render(:test, :hello, :views => settings.views + '/foo') } assert_equal "from another views directory\n", body end it 'takes views directory into consideration for caching' do render_app do render(:test, :hello) + render(:test, :hello, :views => settings.views + '/foo') end assert_equal "Hello World!\nfrom another views directory\n", body end it 'passes locals to the layout' do mock_app do template(:my_layout) { 'Hello <%= name %>!<%= yield %>' } get('/') do erb('

content

', { :layout => :my_layout }, { :name => 'Mike'}) end end get '/' assert ok? assert_equal 'Hello Mike!

content

', body end it 'sets layout-only options via layout_options' do render_app do render(:str, :in_a, :views => settings.views + '/a', :layout_options => { :views => settings.views }, :layout => :layout2) end assert ok? assert_equal "

String Layout!

\nGimme an A!\n", body end it 'loads templates defined in subclasses' do base = Class.new(Sinatra::Base) base.template(:foo) { 'bar' } render_app(base) { render(:test, :foo) } assert ok? assert_equal 'bar', body end it 'allows setting default content type per template engine' do render_app(:str => { :content_type => :txt }) { render :str, 'foo' } assert_equal 'text/plain;charset=utf-8', response['Content-Type'] end it 'setting default content type does not affect other template engines' do render_app(:str => { :content_type => :txt }) { render :test, 'foo' } assert_equal 'text/html;charset=utf-8', response['Content-Type'] end it 'setting default content type per template engine does not override content_type' do render_app :str => { :content_type => :txt } do content_type :html render :str, 'foo' end assert_equal 'text/html;charset=utf-8', response['Content-Type'] end it 'uses templates in superclasses before subclasses' do base = Class.new(Sinatra::Base) base.template(:foo) { 'template in superclass' } assert_equal 'template in superclass', base.templates[:foo].first.call mock_app(base) do set :views, File.dirname(__FILE__) + '/views' template(:foo) { 'template in subclass' } get('/') { render :test, :foo } end assert_equal 'template in subclass', @app.templates[:foo].first.call get '/' assert ok? assert_equal 'template in subclass', body end it "is possible to use a different engine for the layout than for the template itself explicitly" do render_app do settings.template(:layout) { 'Hello <%= yield %>!' } render :str, "<%= 'World' %>", :layout_engine => :erb end assert_equal "Hello <%= 'World' %>!", body end it "is possible to use a different engine for the layout than for the template itself globally" do render_app :str => { :layout_engine => :erb } do settings.template(:layout) { 'Hello <%= yield %>!' } render :str, "<%= 'World' %>" end assert_equal "Hello <%= 'World' %>!", body end it "does not leak the content type to the template" do render_app :str => { :layout_engine => :erb } do settings.template(:layout) { 'Hello <%= yield %>!' } render :str, "<%= 'World' %>", :content_type => :txt end assert_equal "text/html;charset=utf-8", headers['Content-Type'] end it "is possible to register another template" do Tilt.register "html.erb", Tilt[:erb] render_app { render :erb, :calc } assert_equal '2', body end it "passes scope to the template" do mock_app do template(:scoped) { 'Hello <%= foo %>' } get('/') do some_scope = Object.new def some_scope.foo() 'World!' end erb :scoped, :scope => some_scope end end get '/' assert ok? assert_equal 'Hello World!', body end it "is possible to use custom logic for finding template files" do mock_app do set :views, ["a", "b"].map { |d| File.dirname(__FILE__) + '/views/' + d } def find_template(views, name, engine, &block) Array(views).each { |v| super(v, name, engine, &block) } end get('/:name') { render(:str, params[:name].to_sym) } end get '/in_a' assert_body 'Gimme an A!' get '/in_b' assert_body 'Gimme a B!' end end # __END__ : this is not the real end of the script. __END__ @@ foo this is foo @@ bar There's a space after 'bar'! @@ foo bar this is not foo @@ umlaut Den som tror at hemma det är där man bor har aldrig vart hos mig. @@ layout X = yield X sinatra-1.4.3/test/extensions_test.rb0000644000004100000410000000531012161612727017756 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) class ExtensionsTest < Test::Unit::TestCase module FooExtensions def foo end private def im_hiding_in_ur_foos end end module BarExtensions def bar end end module BazExtensions def baz end end module QuuxExtensions def quux end end module PainExtensions def foo=(name); end def bar?(name); end def fizz!(name); end end it 'will add the methods to the DSL for the class in which you register them and its subclasses' do Sinatra::Base.register FooExtensions assert Sinatra::Base.respond_to?(:foo) Sinatra::Application.register BarExtensions assert Sinatra::Application.respond_to?(:bar) assert Sinatra::Application.respond_to?(:foo) assert !Sinatra::Base.respond_to?(:bar) end it 'allows extending by passing a block' do Sinatra::Base.register { def im_in_ur_anonymous_module; end } assert Sinatra::Base.respond_to?(:im_in_ur_anonymous_module) end it 'will make sure any public methods added via Application#register are delegated to Sinatra::Delegator' do Sinatra::Application.register FooExtensions assert Sinatra::Delegator.private_instance_methods. map { |m| m.to_sym }.include?(:foo) assert !Sinatra::Delegator.private_instance_methods. map { |m| m.to_sym }.include?(:im_hiding_in_ur_foos) end it 'will handle special method names' do Sinatra::Application.register PainExtensions assert Sinatra::Delegator.private_instance_methods. map { |m| m.to_sym }.include?(:foo=) assert Sinatra::Delegator.private_instance_methods. map { |m| m.to_sym }.include?(:bar?) assert Sinatra::Delegator.private_instance_methods. map { |m| m.to_sym }.include?(:fizz!) end it 'will not delegate methods on Base#register' do Sinatra::Base.register QuuxExtensions assert !Sinatra::Delegator.private_instance_methods.include?("quux") end it 'will extend the Sinatra::Application application by default' do Sinatra.register BazExtensions assert !Sinatra::Base.respond_to?(:baz) assert Sinatra::Application.respond_to?(:baz) end module BizzleExtension def bizzle bizzle_option end def self.registered(base) fail "base should be BizzleApp" unless base == BizzleApp fail "base should have already extended BizzleExtension" unless base.respond_to?(:bizzle) base.set :bizzle_option, 'bizzle!' end end class BizzleApp < Sinatra::Base end it 'sends .registered to the extension module after extending the class' do BizzleApp.register BizzleExtension assert_equal 'bizzle!', BizzleApp.bizzle_option assert_equal 'bizzle!', BizzleApp.bizzle end end sinatra-1.4.3/test/contest.rb0000644000004100000410000000674512161612727016214 0ustar www-datawww-data# Copyright (c) 2009 Damian Janowski and Michel Martens for Citrusbyte # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require "test/unit" # Test::Unit loads a default test if the suite is empty, whose purpose is to # fail. Since having empty contexts is a common practice, we decided to # overwrite TestSuite#empty? in order to allow them. Having a failure when no # tests have been defined seems counter-intuitive. class Test::Unit::TestSuite def empty? false end end # Contest adds +teardown+, +test+ and +context+ as class methods, and the # instance methods +setup+ and +teardown+ now iterate on the corresponding # blocks. Note that all setup and teardown blocks must be defined with the # block syntax. Adding setup or teardown instance methods defeats the purpose # of this library. class Test::Unit::TestCase def self.setup(&block) setup_blocks << block end def self.teardown(&block) teardown_blocks << block end def self.setup_blocks() @setup_blocks ||= [] end def self.teardown_blocks() @teardown_blocks ||= [] end def setup_blocks(base = self.class) setup_blocks base.superclass if base.superclass.respond_to? :setup_blocks base.setup_blocks.each do |block| instance_eval(&block) end end def teardown_blocks(base = self.class) teardown_blocks base.superclass if base.superclass.respond_to? :teardown_blocks base.teardown_blocks.each do |block| instance_eval(&block) end end alias setup setup_blocks alias teardown teardown_blocks def self.context(*name, &block) subclass = Class.new(self) remove_tests(subclass) subclass.class_eval(&block) if block_given? const_set(context_name(name.join(" ")), subclass) end def self.test(name, &block) define_method(test_name(name), &block) end class << self alias_method :should, :test alias_method :describe, :context end private def self.context_name(name) # "Test#{sanitize_name(name).gsub(/(^| )(\w)/) { $2.upcase }}".to_sym name = "Test#{sanitize_name(name).gsub(/(^| )(\w)/) { $2.upcase }}" name.tr(" ", "_").to_sym end def self.test_name(name) name = "test_#{sanitize_name(name).gsub(/\s+/,'_')}_0" name = name.succ while method_defined? name name.to_sym end def self.sanitize_name(name) # name.gsub(/\W+/, ' ').strip name.gsub(/\W+/, ' ') end def self.remove_tests(subclass) subclass.public_instance_methods.grep(/^test_/).each do |meth| subclass.send(:undef_method, meth.to_sym) end end end sinatra-1.4.3/test/route_added_hook_test.rb0000644000004100000410000000252212161612727021060 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) module RouteAddedTest @routes, @procs = [], [] def self.routes ; @routes ; end def self.procs ; @procs ; end def self.route_added(verb, path, proc) @routes << [verb, path] @procs << proc end end class RouteAddedHookTest < Test::Unit::TestCase setup do RouteAddedTest.routes.clear RouteAddedTest.procs.clear end it "should be notified of an added route" do mock_app(Class.new(Sinatra::Base)) do register RouteAddedTest get('/') {} end assert_equal [["GET", "/"], ["HEAD", "/"]], RouteAddedTest.routes end it "should include hooks from superclass" do a = Class.new(Class.new(Sinatra::Base)) b = Class.new(a) a.register RouteAddedTest b.class_eval { post("/sub_app_route") {} } assert_equal [["POST", "/sub_app_route"]], RouteAddedTest.routes end it "should only run once per extension" do mock_app(Class.new(Sinatra::Base)) do register RouteAddedTest register RouteAddedTest get('/') {} end assert_equal [["GET", "/"], ["HEAD", "/"]], RouteAddedTest.routes end it "should pass route blocks as an argument" do mock_app(Class.new(Sinatra::Base)) do register RouteAddedTest get('/') {} end assert_kind_of Proc, RouteAddedTest.procs.first end end sinatra-1.4.3/test/static_test.rb0000644000004100000410000001602212161612727017050 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) class StaticTest < Test::Unit::TestCase setup do mock_app do set :static, true set :public_folder, File.dirname(__FILE__) end end it 'serves GET requests for files in the public directory' do get "/#{File.basename(__FILE__)}" assert ok? assert_equal File.read(__FILE__), body assert_equal File.size(__FILE__).to_s, response['Content-Length'] assert response.headers.include?('Last-Modified') end it 'produces a body that can be iterated over multiple times' do env = Rack::MockRequest.env_for("/#{File.basename(__FILE__)}") status, headers, body = @app.call(env) buf1, buf2 = [], [] body.each { |part| buf1 << part } body.each { |part| buf2 << part } assert_equal buf1.join, buf2.join assert_equal File.read(__FILE__), buf1.join end it 'sets the sinatra.static_file env variable if served' do env = Rack::MockRequest.env_for("/#{File.basename(__FILE__)}") status, headers, body = @app.call(env) assert_equal File.expand_path(__FILE__), env['sinatra.static_file'] end it 'serves HEAD requests for files in the public directory' do head "/#{File.basename(__FILE__)}" assert ok? assert_equal '', body assert response.headers.include?('Last-Modified') assert_equal File.size(__FILE__).to_s, response['Content-Length'] end %w[POST PUT DELETE].each do |verb| it "does not serve #{verb} requests" do send verb.downcase, "/#{File.basename(__FILE__)}" assert_equal 404, status end end it 'serves files in preference to custom routes' do @app.get("/#{File.basename(__FILE__)}") { 'Hello World' } get "/#{File.basename(__FILE__)}" assert ok? assert body != 'Hello World' end it 'does not serve directories' do get "/" assert not_found? end it 'passes to the next handler when the static option is disabled' do @app.set :static, false get "/#{File.basename(__FILE__)}" assert not_found? end it 'passes to the next handler when the public option is nil' do @app.set :public_folder, nil get "/#{File.basename(__FILE__)}" assert not_found? end it '404s when a file is not found' do get "/foobarbaz.txt" assert not_found? end it 'serves files when .. path traverses within public directory' do get "/data/../#{File.basename(__FILE__)}" assert ok? assert_equal File.read(__FILE__), body end it '404s when .. path traverses outside of public directory' do mock_app do set :static, true set :public_folder, File.dirname(__FILE__) + '/data' end get "/../#{File.basename(__FILE__)}" assert not_found? end def assert_valid_range(http_range, range, path, file) request = Rack::MockRequest.new(@app) response = request.get("/#{File.basename(path)}", 'HTTP_RANGE' => http_range) should_be = file[range] expected_range = "bytes #{range.begin}-#{range.end}/#{file.length}" assert_equal( 206,response.status, "Should be HTTP/1.1 206 Partial content" ) assert_equal( should_be.length, response.body.length, "Unexpected response length for #{http_range}" ) assert_equal( should_be, response.body, "Unexpected response data for #{http_range}" ) assert_equal( should_be.length.to_s, response['Content-Length'], "Incorrect Content-Length for #{http_range}" ) assert_equal( expected_range, response['Content-Range'], "Incorrect Content-Range for #{http_range}" ) end it 'handles valid byte ranges correctly' do # Use the biggest file in this dir so we can test ranges > 8k bytes. (StaticFile sends in 8k chunks.) path = File.dirname(__FILE__) + '/helpers_test.rb' # currently 16k bytes file = File.read(path) length = file.length assert length > 9000, "The test file #{path} is too short (#{length} bytes) to run these tests" [0..0, 42..88, 1234..1234, 100..9000, 0..(length-1), (length-1)..(length-1)].each do |range| assert_valid_range("bytes=#{range.begin}-#{range.end}", range, path, file) end [0, 100, length-100, length-1].each do |start| assert_valid_range("bytes=#{start}-", (start..length-1), path, file) end [1, 100, length-100, length-1, length].each do |range_length| assert_valid_range("bytes=-#{range_length}", (length-range_length..length-1), path, file) end # Some valid ranges that exceed the length of the file: assert_valid_range("bytes=100-999999", (100..length-1), path, file) assert_valid_range("bytes=100-#{length}", (100..length-1), path, file) assert_valid_range("bytes=-#{length}", (0..length-1), path, file) assert_valid_range("bytes=-#{length+1}", (0..length-1), path, file) assert_valid_range("bytes=-999999", (0..length-1), path, file) end it 'correctly ignores syntactically invalid range requests' do # ...and also ignores multi-range requests, which aren't supported yet ["bytes=45-40", "bytes=IV-LXVI", "octets=10-20", "bytes=-", "bytes=1-2,3-4"].each do |http_range| request = Rack::MockRequest.new(@app) response = request.get("/#{File.basename(__FILE__)}", 'HTTP_RANGE' => http_range) assert_equal( 200, response.status, "Invalid range '#{http_range}' should be ignored" ) assert_equal( nil, response['Content-Range'], "Invalid range '#{http_range}' should be ignored" ) end end it 'returns error 416 for unsatisfiable range requests' do # An unsatisfiable request is one that specifies a start that's at or past the end of the file. length = File.read(__FILE__).length ["bytes=888888-", "bytes=888888-999999", "bytes=#{length}-#{length}"].each do |http_range| request = Rack::MockRequest.new(@app) response = request.get("/#{File.basename(__FILE__)}", 'HTTP_RANGE' => http_range) assert_equal( 416, response.status, "Unsatisfiable range '#{http_range}' should return 416" ) assert_equal( "bytes */#{length}", response['Content-Range'], "416 response should include actual length" ) end end it 'does not include static cache control headers by default' do env = Rack::MockRequest.env_for("/#{File.basename(__FILE__)}") status, headers, body = @app.call(env) assert !headers.has_key?('Cache-Control') end it 'sets cache control headers on static files if set' do @app.set :static_cache_control, :public env = Rack::MockRequest.env_for("/#{File.basename(__FILE__)}") status, headers, body = @app.call(env) assert headers.has_key?('Cache-Control') assert_equal headers['Cache-Control'], 'public' @app.set( :static_cache_control, [:public, :must_revalidate, {:max_age => 300}] ) env = Rack::MockRequest.env_for("/#{File.basename(__FILE__)}") status, headers, body = @app.call(env) assert headers.has_key?('Cache-Control') assert_equal( headers['Cache-Control'], 'public, must-revalidate, max-age=300' ) end end sinatra-1.4.3/README.ko.md0000644000004100000410000021253512161612727015114 0ustar www-datawww-data# Sinatra *주의: 이 문서는 영문판의 번역본이며 최신판 문서와 다를 수 있음.* Sinatra는 최소한의 노력으로 루비 기반 웹 애플리케이션을 신속하게 만들 수 있게 해 주는 [DSL](http://en.wikipedia.org/wiki/Domain-specific_language)이다: ```ruby # myapp.rb require 'sinatra' get '/' do 'Hello world!' end ``` 다음과 같이 젬을 설치하고 실행한다: ```ruby gem install sinatra ruby myapp.rb ``` 확인: http://localhost:4567 `gem install thin`도 함께 실행하기를 권장하며, 그럴 경우 Sinatra는 thin을 부른다. ## 라우터(Routes) Sinatra에서, 라우터(route)는 URL-매칭 패턴과 쌍을 이루는 HTTP 메서드다. 각각의 라우터는 블록과 연결된다: ```ruby get '/' do .. 무언가 보여주기(show) .. end post '/' do .. 무언가 만들기(create) .. end put '/' do .. 무언가 대체하기(replace) .. end patch '/' do .. 무언가 수정하기(modify) .. end delete '/' do .. 무언가 없애기(annihilate) .. end options '/' do .. 무언가 주기(appease) .. end ``` 라우터는 정의된 순서에 따라 매치되며 매칭된 첫 번째 라우터가 호출된다. 라우터 패턴에는 이름을 가진 매개변수가 포함될 수있으며, `params` 해시로 접근할 수 있다: ```ruby get '/hello/:name' do # "GET /hello/foo" 및 "GET /hello/bar"와 매치 # params[:name]은 'foo' 또는 'bar' "Hello #{params[:name]}!" end ``` 또한 블록 매개변수를 통하여도 이름을 가진 매개변수에 접근할 수 있다: ```ruby get '/hello/:name' do |n| "Hello #{n}!" end ``` 라우터 패턴에는 스플랫(splat, 또는 와일드카드)도 포함될 수 있으며, 이럴 경우 `params[:splat]` 배열로 접근할 수 있다: ```ruby get '/say/*/to/*' do # /say/hello/to/world와 매치 params[:splat] # => ["hello", "world"] end get '/download/*.*' do # /download/path/to/file.xml과 매치 params[:splat] # => ["path/to/file", "xml"] end ``` 또는 블록 매개변수도 가능하다: ```ruby get '/download/*.*' do |path, ext| [path, ext] # => ["path/to/file", "xml"] end ``` 정규표현식을 이용한 라우터 매칭: ```ruby get %r{/hello/([\w]+)} do "Hello, #{params[:captures].first}!" end ``` 또는 블록 매개변수로도 가능: ```ruby get %r{/hello/([\w]+)} do |c| "Hello, #{c}!" end ``` 라우터 패턴에는 선택적인(optional) 매개변수도 올 수 있다: ```ruby get '/posts.?:format?' do # "GET /posts" 및 "GET /posts.json", "GET /posts.xml" 와 같은 어떤 확장자와도 매칭 end ``` 한편, 경로 탐색 공격 방지(path traversal attack protection, 아래 참조)를 비활성화시키지 않았다면, 요청 경로는 라우터와 매칭되기 이전에 수정될 수 있다. ### 조건(Conditions) 라우터는 예를 들면 사용자 에이전트(user agent)와 같은 다양한 매칭 조건을 포함할 수 있다: ```ruby get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do "Songbird 버전 #{params[:agent][0]}을 사용하는군요!" end get '/foo' do # songbird 브라우저가 아닌 경우 매치 end ``` 그 밖에 다른 조건으로는 `host_name`과 `provides`가 있다: ```ruby get '/', :host_name => /^admin\./ do "Admin Area, Access denied!" end get '/', :provides => 'html' do haml :index end get '/', :provides => ['rss', 'atom', 'xml'] do builder :feed end ``` 여러분만의 조건도 쉽게 정의할 수 있다: ```ruby set(:probability) { |value| condition { rand <= value } } get '/win_a_car', :probability => 0.1 do "내가 졌소!" end get '/win_a_car' do "미안해서 어쩌나." end ``` 여러 값을 받는 조건에는 스플랫(splat)을 사용하자: ```ruby set(:auth) do |*roles| # <- 이게 스플랫 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 "내 계정 정보" end get "/only/admin/", :auth => :admin do "관리자 외 접근불가!" end ``` ### 반환값(Return Values) 라우터 블록의 반환값은 HTTP 클라이언트로 전달되는 응답 본문을 결정하거나, 또는 Rack 스택에서 다음 번 미들웨어를 결정한다. 대부분의 경우, 이 반환값은 위의 예제에서 보듯 문자열이지만, 다른 값도 가능하다. 유효한 Rack 응답, Rack 본문 객체 또는 HTTP 상태 코드가 되는 어떠한 객체라도 반환할 수 있다: * 세 요소를 가진 배열: `[상태 (Fixnum), 헤더 (Hash), 응답 본문 (#each에 반응)]` * 두 요소를 가진 배열: `[상태 (Fixnum), 응답 본문 (#each에 반응)]` * `#each`에 반응하고 주어진 블록으로 문자열만을 전달하는 객체 * 상태 코드를 의미하는 Fixnum 이에 따라 우리는, 예를 들면, 스트리밍(streaming) 예제를 쉽게 구현할 수 있다: ```ruby class Stream def each 100.times { |i| yield "#{i}\n" } end end get('/') { Stream.new } ``` 이런 번거로움을 줄이기 위해 `stream` 헬퍼 메서드(아래 참조)를 사용하여 스트리밍 로직을 라우터 속에 둘 수도 있다. ### 커스텀 라우터 매처(Custom Route Matchers) 위에서 보듯, Sinatra에는 문자열 패턴 및 정규표현식을 이용한 라우터 매칭 지원이 내장되어 있다. 그렇지만, 그게 끝이 아니다. 여러분 만의 매처(matcher)도 쉽게 정의할 수 있다: ```ruby class AllButPattern Match = Struct.new(:captures) def initialize(except) @except = except @captures = Match.new([]) end def match(str) @captures unless @except === str end end def all_but(pattern) AllButPattern.new(pattern) end get all_but("/index") do # ... end ``` 사실 위의 예제는 조금 과하게 작성된 면이 있다. 다음과 같이 표현할 수도 있다: ```ruby get // do pass if request.path_info == "/index" # ... end ``` 또는 네거티브 룩어헤드(negative look ahead)를 사용할 수도 있다: ```ruby get %r{^(?!/index$)} do # ... end ``` ## 정적 파일(Static Files) 정적 파일들은 `./public`에서 제공된다. 위치를 다른 곳으로 변경하려면 `:public_folder` 옵션을 사용하면 된다: ```ruby set :public_folder, File.dirname(__FILE__) + '/static' ``` 이 때 public 디렉터리명은 URL에 포함되지 않는다는 점에 유의. `./public/css/style.css` 파일은 `http://example.com/css/style.css` 로 접근할 수 있다. `Cache-Control` 헤더 정보를 추가하려면 `:static_cache_control` 설정(아래 참조)을 사용하면 된다. ## 뷰 / 템플릿(Views / Templates) 각 템플릿 언어는 그들만의 고유한 렌더링 메서드를 통해 표출된다. 이들 메서드는 단순히 문자열을 반환한다. ```ruby get '/' do erb :index end ``` 이 메서드는 `views/index.erb`를 렌더한다. 템플릿 이름 대신 템플릿의 내용을 직접 전달할 수도 있다: ```ruby get '/' do code = "<%= Time.now %>" erb code end ``` 템플릿은 두 번째 인자로 옵션값의 해시를 받는다: ```ruby get '/' do erb :index, :layout => :post end ``` 이렇게 하면 `views/post.erb` 속에 내장된 `views/index.erb`를 렌더한다. (기본값은 `views/layout.erb`이며, 이 파일이 존재할 경우에만 먹는다). Sinatra가 이해하지 못하는 모든 옵션값들은 템플릿 엔진으로 전달될 것이다: ```ruby get '/' do haml :index, :format => :html5 end ``` 옵션값은 템플릿 언어별로 일반적으로 설정할 수도 있다: ```ruby set :haml, :format => :html5 get '/' do haml :index end ``` render 메서드에서 전달된 옵션값들은 `set`을 통해 설정한 옵션값을 덮어 쓴다. 가능한 옵션값들:
locals
문서로 전달되는 local 목록. 파셜과 함께 사용하기 좋음. 예제: erb "<%= foo %>", :locals => {:foo => "bar"}
default_encoding
불확실한 경우에 사용할 문자열 인코딩. 기본값은 settings.default_encoding.
views
템플릿을 로드할 뷰 폴더. 기본값은 settings.views.
layout
레이아웃을 사용할지 여부 (true 또는 false), 만약 이 값이 심볼일 경우, 사용할 템플릿을 지정. 예제: erb :index, :layout => !request.xhr?
content_type
템플릿이 생성하는 Content-Type, 기본값은 템플릿 언어에 의존.
scope
템플릿을 렌더링하는 범위. 기본값은 어플리케이션 인스턴스. 만약 이 값을 변경하면, 인스턴스 변수와 헬퍼 메서드들을 사용할 수 없게 됨.
layout_engine
레이아웃 렌더링에 사용할 템플릿 엔진. 레이아웃을 지원하지 않는 언어인 경우에 유용. 기본값은 템플릿에서 사용하는 엔진. 예제: set :rdoc, :layout_engine => :erb
템플릿은 `./views` 아래에 놓이는 것으로 가정됨. 만약 뷰 디렉터리를 다른 곳으로 두려면: ```ruby set :views, settings.root + '/templates' ``` 꼭 알아야 할 중요한 점 한 가지는 템플릿은 언제나 심볼로 참조된다는 것이며, 템플릿이 하위 디렉터리에 위치한 경우라도 마찬가지임(그럴 경우에는 `:'subdir/template'`을 사용). 반드시 심볼이어야 하는 이유는, 만약 그렇게 하지 않으면 렌더링 메서드가 전달된 문자열을 직접 렌더하려 할 것이기 때문임. ### 가능한 템플릿 언어들(Available Template Languages) 일부 언어는 여러 개의 구현이 있음. 어느 구현을 사용할지 저정하려면(그리고 스레드-안전thread-safe 모드로 하려면), 먼저 require 시키기만 하면 됨: ```ruby require 'rdiscount' # or require 'bluecloth' get('/') { markdown :index } ``` ### Haml 템플릿
의존 haml
파일 확장자 .haml
haml :index, :format => :html5
### Erb 템플릿
의존 erubis 또는 erb (루비 속에 포함)
파일 확장자 .erb, .rhtml 또는 .erubis (Erubis만 해당)
예제 erb :index
### Builder 템플릿
의존 builder
파일 확장자 .builder
Example builder { |xml| xml.em "hi" }
인라인 템플릿으로 블록을 받음(예제 참조). ### Nokogiri 템플릿
의존 nokogiri
파일 확장자 .nokogiri
예제 nokogiri { |xml| xml.em "hi" }
인라인 템플릿으로 블록을 받음(예제 참조). ### Sass 템플릿
의존 sass
파일 확장자 .sass
예제 sass :stylesheet, :style => :expanded
### SCSS 템플릿
의존 sass
파일 확장자 .scss
예제 scss :stylesheet, :style => :expanded
### Less 템플릿
의존 less
파일 확장자 .less
예제 less :stylesheet
### Liquid 템플릿
의존 liquid
파일 확장자 .liquid
예제 liquid :index, :locals => { :key => 'value' }
Liquid 템플릿에서는 루비 메서드(`yield` 제외)를 호출할 수 없기 때문에, 거의 대부분의 경우 locals를 전달해야 함. ### Markdown 템플릿
의존 rdiscount, redcarpet, bluecloth, kramdown *또는* maruku
파일 확장 .markdown, .mkd, .md
예제 markdown :index, :layout_engine => :erb
마크다운에서는 메서드 호출 뿐 아니라 locals 전달도 안됨. 따라서 일반적으로는 다른 렌더링 엔진과 함께 사용하게 될 것임: ```ruby erb :overview, :locals => { :text => markdown(:introduction) } ``` 또한 다른 템플릿 속에서 `markdown` 메서드를 호출할 수도 있음: ```ruby %h1 안녕 Haml! %p= markdown(:greetings) ``` Markdown에서 루비를 호출할 수 없기 때문에, Markdown으로 작성된 레이아웃은 사용할 수 없음. 단, `:layout_engine` 옵션으로 템플릿의 레이아웃은 다른 렌더링 엔진을 사용하는 것은 가능. ### Textile 템플릿
의존 RedCloth
파일 확장자 .textile
예제 textile :index, :layout_engine => :erb
Textile에서 메서드를 호출하거나 locals를 전달하는 것은 불가능함. 따라서 일반적으로 다른 렌더링 엔진과 함께 사용하게 될 것임: ```ruby erb :overview, :locals => { :text => textile(:introduction) } ``` 또한 다른 템플릿 속에서 `textile` 메서드를 호출할 수도 있음: ```ruby %h1 안녕 Haml! %p= textile(:greetings) ``` Textile에서 루비를 호출할 수 없기 때문에, Textile로 작성된 레이아웃은 사용할 수 없음. 단, `:layout_engine` 옵션으로 템플릿의 레이아웃은 다른 렌더링 엔진을 사용하는 것은 가능. ### RDoc 템플릿
의존 rdoc
파일 확장자 .rdoc
예제 rdoc :README, :layout_engine => :erb
rdoc에서 메서드를 호출하거나 locals를 전달하는 것은 불가능함. 따라서 일반적으로 다른 렌더링 엔진과 함께 사용하게 될 것임: ```ruby erb :overview, :locals => { :text => rdoc(:introduction) } ``` 또한 다른 템플릿 속에서 `rdoc` 메서드를 호출할 수도 있음: ```ruby %h1 Hello From Haml! %p= rdoc(:greetings) ``` RDoc에서 루비를 호출할 수 없기 때문에, RDoc로 작성된 레이아웃은 사용할 수 없음. 단, `:layout_engine` 옵션으로 템플릿의 레이아웃은 다른 렌더링 엔진을 사용하는 것은 가능. ### Radius 템플릿
의존 radius
파일 확장자 .radius
예제 radius :index, :locals => { :key => 'value' }
Radius 템플릿에서는 루비 메서드를 호출할 수 없기 때문에, 거의 대부분의 경우 locals로 전달하게 될 것임. ### Markaby 템플릿
의존 markaby
파일확장 .mab
예제 markaby { h1 "Welcome!" }
인라인 템플릿으로 블록을 받을 수도 있음(예제 참조). ### RABL 템플릿
의존 rabl
파일 확장자 .rabl
예제 rabl :index
### Slim 템플릿
의존 slim
파일 확장자 .slim
예제 slim :index
### Creole 템플릿
의존 creole
파일 확장자 .creole
예제 creole :wiki, :layout_engine => :erb
creole에서는 루비 메서드를 호출할 수 없고 locals도 전달할 수 없음. 따라서 일반적으로는 다른 렌더링 엔진과 함께 사용하게 될 것임. ```ruby erb :overview, :locals => { :text => creole(:introduction) } ``` 또한 다른 템플릿 속에서 `creole` 메서드를 호출할 수도 있음: ```ruby %h1 Hello From Haml! %p= creole(:greetings) ``` Creole에서 루비를 호출할 수 없기 때문에, Creole로 작성된 레이아웃은 사용할 수 없음. 단, `:layout_engine` 옵션으로 템플릿의 레이아웃은 다른 렌더링 엔진을 사용하는 것은 가능. ### CoffeeScript 템플릿
의존성 coffee-script자바스크립트 실행법
파일 확장자 .coffee
예제 coffee :index
### Yajl 템플릿
의존 yajl-ruby
파일 확장자 .yajl
예제 yajl :index, :locals => { :key => 'qux' }, :callback => 'present', :variable => 'resource'
The template source is evaluated as a Ruby string, and the resulting json variable is converted #to_json. 템플릿 소스는 루비 문자열로 평가(evaluate)되고, 결과인 json 변수는 #to_json으로 변환됨. ```ruby json = { :foo => 'bar' } json[:baz] = key ``` `:callback`과 `:variable` 옵션은 렌더된 객체를 꾸미는데(decorate) 사용할 수 있음. ```ruby var resource = {"foo":"bar","baz":"qux"}; present(resource); ``` ### 내장된(Embedded) 템플릿 ```ruby get '/' do haml '%div.title Hello World' end ``` 내장된 템플릿 문자열을 렌더함. ### 템플릿에서 변수에 접근하기 Templates are evaluated within the same context as route handlers. Instance variables set in route handlers are directly accessible by templates: 템플릿은 라우터 핸들러와 같은 맥락(context)에서 평가된다. 라우터 핸들러에서 설정한 인스턴스 변수들은 템플릿에서 접근 가능하다: ```ruby get '/:id' do @foo = Foo.find(params[:id]) haml '%h1= @foo.name' end ``` 또는, 명시적으로 로컬 변수의 해시를 지정: ```ruby get '/:id' do foo = Foo.find(params[:id]) haml '%h1= bar.name', :locals => { :bar => foo } end ``` This is typically used when rendering templates as partials from within other templates. 이 방법은 통상적으로 템플릿을 다른 템플릿 속에서 파셜(partial)로 렌더링할 때 사용된다. ### 인라인 템플릿 템플릿은 소스 파일의 마지막에서 정의할 수도 있다: ```ruby require 'sinatra' get '/' do haml :index end __END__ @@ layout %html = yield @@ index %div.title Hello world. ``` 참고: require sinatra 시킨 소스 파일에 정의된 인라인 템플릿은 자동으로 로드된다. 다른 소스 파일에서 인라인 템플릿을 사용하려면 명시적으로 `enable :inline_templates`을 호출하면 됨. ### 이름을 가지는 템플릿(Named Templates) 템플릿은 톱 레벨(top-level)에서 `template`메서드를 사용하여 정의할 수 있다: ```ruby template :layout do "%html\n =yield\n" end template :index do '%div.title Hello World!' end get '/' do haml :index end ``` "layout"이라는 이름의 템플릿이 존재하면, 매번 템플릿이 렌더될 때마다 사용될 것이다. 이 때 `:layout => false`를 전달하여 개별적으로 레이아웃을 비활성시키거나 또는 `set :haml, :layout => false`으로 기본값을 비활성으로 둘 수 있다: ```ruby get '/' do haml :index, :layout => !request.xhr? end ``` ### 파일 확장자 연결하기 어떤 파일 확장자를 특정 템플릿 엔진과 연결하려면, `Tilt.register`를 사용하면 된다. 예를 들어, `tt`라는 파일 확장자를 Textile 템플릿과 연결하고 싶다면, 다음과 같이 하면 된다: ```ruby Tilt.register :tt, Tilt[:textile] ``` ### 나만의 고유한 템플릿 엔진 추가하기 우선, Tilt로 여러분 엔진을 등록하고, 그런 다음 렌더링 메서드를 생성하자: ```ruby Tilt.register :myat, MyAwesomeTemplateEngine helpers do def myat(*args) render(:myat, *args) end end get '/' do myat :index end ``` `./views/index.myat` 를 렌더함. Tilt에 대한 더 자세한 내용은 https://github.com/rtomayko/tilt 참조. ## 필터(Filters) 사전 필터(before filter)는 라우터와 동일한 맥락에서 매 요청 전에 평가되며 요청과 응답을 변형할 수 있다. 필터에서 설정된 인스턴스 변수들은 라우터와 템플릿 속에서 접근 가능하다: ```ruby before do @note = 'Hi!' request.path_info = '/foo/bar/baz' end get '/foo/*' do @note #=> 'Hi!' params[:splat] #=> 'bar/baz' end ``` 사후 필터(after filter)는 라우터와 동일한 맥락에서 매 요청 이후에 평가되며 마찬가지로 요청과 응답을 변형할 수 있다. 사전 필터와 라우터에서 설정된 인스턴스 변수들은 사후 필터에서 접근 가능하다: ```ruby after do puts response.status end ``` 참고: 만약 라우터에서 `body` 메서드를 사용하지 않고 그냥 문자열만 반환한 경우라면, body는 나중에 생성되는 탓에, 아직 사후 필터에서 사용할 수 없을 것이다. 필터는 선택적으로 패턴을 취할 수 있으며, 이 경우 요청 경로가 그 패턴과 매치할 경우에만 필터가 평가될 것이다. ```ruby before '/protected/*' do authenticate! end after '/create/:slug' do |slug| session[:last_slug] = slug end ``` 라우터와 마찬가지로, 필터 역시 조건을 갖는다: ```ruby before :agent => /Songbird/ do # ... end after '/blog/*', :host_name => 'example.com' do # ... end ``` ## 헬퍼(Helpers) 톱-레벨의 `helpers` 메서드를 사용하여 라우터 핸들러와 템플릿에서 사용할 헬퍼 메서드들을 정의할 수 있다: ```ruby helpers do def bar(name) "#{name}bar" end end get '/:name' do bar(params[:name]) end ``` 또는, 헬퍼 메서드는 별도의 모듈 속에 정의할 수도 있다: ```ruby module FooUtils def foo(name) "#{name}foo" end end module BarUtils def bar(name) "#{name}bar" end end helpers FooUtils, BarUtils ``` 이 경우 모듈을 애플리케이션 클래스에 포함(include)시킨 것과 동일한 효과를 갖는다. ### 세션(Sessions) 사용하기 세션은 요청 동안에 상태를 유지하기 위해 사용한다. 세션이 활성화되면, 사용자 세션 당 session 해시 하나씩을 갖게 된다: ```ruby enable :sessions get '/' do "value = " << session[:value].inspect end get '/:value' do session[:value] = params[:value] end ``` `enable :sessions`은 실은 모든 데이터를 쿠키 속에 저장함에 유의하자. 항상 이렇게 하고 싶지 않을 수도 있을 것이다(예를 들어, 많은 양의 데이터를 저장하게 되면 트래픽이 높아진다). 이 때는 여러 가지 랙 세션 미들웨어(Rack session middleware)를 사용할 수 있을 것이다: 이렇게 할 경우라면, `enable :sessions`을 호출하지 *말고*, 대신 여러분이 선택한 미들웨어를 다른 모든 미들웨어들처럼 포함시키면 된다: ```ruby use Rack::Session::Pool, :expire_after => 2592000 get '/' do "value = " << session[:value].inspect end get '/:value' do session[:value] = params[:value] end ``` 보안을 위해서, 쿠키 속의 세션 데이터는 세션 시크릿(secret)으로 사인(sign)된다. Sinatra는 여러분을 위해 무작위 시크릿을 생성한다. 그렇지만, 이 시크릿은 여러분 애플리케이션 시작 시마다 변경될 수 있기 때문에, 여러분은 여러분 애플리케이션의 모든 인스턴스들이 공유할 시크릿을 직접 만들고 싶을 수도 있다: ```ruby set :session_secret, 'super secret' ``` 조금 더 세부적인 설정이 필요하다면, `sessions` 설정에서 옵션이 있는 해시를 저장할 수도 있을 것이다: ```ruby set :sessions, :domain => 'foo.com' ``` ### 중단하기(Halting) 필터나 라우터에서 요청을 즉각 중단하고 싶을 때 사용하라: ```ruby halt ``` 중단할 때 상태를 지정할 수도 있다: ```ruby halt 410 ``` 또는 본문을 넣을 수도 있다: ```ruby halt 'this will be the body' ``` 또는 둘 다도 가능하다: ```ruby halt 401, 'go away!' ``` 헤더를 추가할 경우에는 다음과 같이 하면 된다: ```ruby halt 402, {'Content-Type' => 'text/plain'}, 'revenge' ``` 물론 `halt`를 템플릿과 결합하는 것도 가능하다: ```ruby halt erb(:error) ``` ### 넘기기(Passing) 라우터는 `pass`를 사용하여 다음 번 매칭되는 라우터로 처리를 넘길 수 있다: ```ruby get '/guess/:who' do pass unless params[:who] == 'Frank' 'You got me!' end get '/guess/*' do 'You missed!' end ``` 이 떄 라우터 블록에서 즉각 빠져나오게 되고 제어는 다음 번 매칭되는 라우터로 넘어간다. 만약 매칭되는 라우터를 찾지 못하면, 404가 반환된다. ### 다른 라우터 부르기(Triggering Another Route) 경우에 따라서는 `pass`가 아니라, 다른 라우터를 호출한 결과를 얻고 싶은 경우도 있을 것이다. 이 때는 간단하게 +`call`+을 사용하면 된다: ```ruby get '/foo' do status, headers, body = call env.merge("PATH_INFO" => '/bar') [status, headers, body.map(&:upcase)] end get '/bar' do "bar" end ``` 위 예제의 경우, `"bar"`를 헬퍼로 옮겨 `/foo`와 `/bar` 모두에서 사용하도록 함으로써 테스팅을 쉽게 하고 성능을 높일 수 있을 것이다. 만약 그 요청이 사본이 아닌 바로 그 동일 인스턴스로 보내지도록 하고 싶다면, `call` 대신 `call!`을 사용하면 된다. `call`에 대한 더 자세한 내용은 Rack 명세를 참고하면 된다. ### 본문, 상태 코드 및 헤더 설정하기 라우터 블록의 반환값과 함께 상태 코드(status code)와 응답 본문(response body)을 설정하는 것은 가능하기도 하거니와 권장되는 방법이다. 그렇지만, 경우에 따라서는 본문을 실행 흐름 중의 임의 지점에서 설정하고 싶을 수도 있다. 이 때는 `body` 헬퍼 메서드를 사용하면 된다. 이렇게 하면, 그 순간부터 본문에 접근할 때 그 메서드를 사용할 수가 있다: ```ruby get '/foo' do body "bar" end after do puts body end ``` `body`로 블록을 전달하는 것도 가능하며, 이 블록은 랙(Rack) 핸들러에 의해 실행될 것이다. (이 방법은 스트리밍을 구현할 때 사용할 수 있는데, "값 반환하기"를 참고). 본문와 마찬가지로, 상태코드와 헤더도 설정할 수 있다: ```ruby get '/foo' do status 418 headers \ "Allow" => "BREW, POST, GET, PROPFIND, WHEN", "Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt" body "I'm a tea pot!" end ``` `body`처럼, `header`와 `status`도 매개변수 없이 사용하여 그것의 현재 값을 액세스하는 데 사용될 수 있다. ### 응답 스트리밍(Streaming Responses) 응답 본문의 일정 부분을 계속 생성하는 가운데 데이터를 내보내기 시작하고 싶을 경우도 있을 것이다. 극단적인 예제로, 클라이언트가 접속을 끊기 전까지 계속 데이터를 내보내고 싶을 수도 있다. 여러분만의 래퍼(wrapper)를 만들기 싫다면 `stream` 헬퍼를 사용하면 된다: ```ruby 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 ``` 이렇게 하면 스트리밍 API나 [서버 발송 이벤트Server Sent Events](http://dev.w3.org/html5/eventsource/)를 구현할 수 있게 해 주며, [WebSockets](http://en.wikipedia.org/wiki/WebSocket)을 위한 기반으로 사용될 수 있다. 또한 이 방법은 일부 콘텐츠가 느린 자원에 의존하는 경우에 스로풋(throughtput)을 높이기 위해 사용될 수도 있다. 스트리밍 동작, 특히 동시 요청의 수는 애플리케이션을 서빙하는 웹서버에 크게 의존적이다. 어떤 서버, 예컨대 WEBRick 같은 경우는 아예 스트리밍을 지원조차 하지 못할 것이다. 만약 서버가 스트리밍을 지원하지 않는다면, 본문은 `stream` 으로 전달된 블록이 수행을 마친 후에 한꺼번에 반환될 것이다. 스트리밍은 Shotgun에서는 작동하지 않는다. 만약 선택적 매개변수 `keep_open`이 설정되어 있다면, 스트림 객체에서 `close`를 호출하지 않을 것이고, 따라서 여러분은 나중에 실행 흐름 상의 어느 시점에서 스트림을 닫을 수 있다. 이 옵션은 Thin과 Rainbow 같은 이벤트 기반 서버에서만 작동한다. 다른 서버들은 여전히 스트림을 닫을 것이다: ```ruby set :server, :thin connections = [] get '/' do # 스트림을 열린 채 유지 stream(:keep_open) { |out| connections << out } end post '/' do # 모든 열린 스트림에 쓰기 connections.each { |out| out << params[:message] << "\n" } "message sent" end ``` ### 로깅(Logging) In the request scope, the `logger` helper exposes a `Logger` instance: 요청 스코프(request scope) 내에서, `Logger`의 인스턴스인 `logger` 헬퍼를 사용할 수 있다: ```ruby get '/' do logger.info "loading data" # ... end ``` 이 로거는 여러분이 Rack 핸들러에서 설정한 로그 셋팅을 자동으로 참고한다. 만약 로깅이 비활성이라면, 이 메서드는 더미(dummy) 객체를 반환할 것이며, 따라서 여러분은 라우터나 필터에서 이 부분에 대해 걱정할 필요는 없다. 로깅은 `Sinatra::Application`에서만 기본으로 활성화되어 있음에 유의하자. 만약 `Sinatra::Base`로부터 상속받은 경우라면 직접 활성화시켜 줘야 한다: ```ruby class MyApp < Sinatra::Base configure :production, :development do enable :logging end end ``` 어떠한 로깅 미들웨어도 설정되지 않게 하려면, `logging` 설정을 `nil`로 두면 된다. 그렇지만, 이럴 경우 `logger`는 `nil`을 반환할 것임에 유의하자. 통상적인 유스케이스는 여러분만의 로거를 사용하고자 할 경우일 것이다. Sinatra는 `env['rack.logger']`에서 찾은 것을 사용할 것이다. ### 마임 타입(Mime Types) `send_file`이나 정적인 파일을 사용할 때에 Sinatra가 인식하지 못하는 마임 타입이 있을 수 있다. 이 경우 `mime_type`을 사용하여 파일 확장자를 등록하면 된다: ```ruby configure do mime_type :foo, 'text/foo' end ``` 또는 `content_type` 헬퍼와 함께 사용할 수도 있다: ```ruby get '/' do content_type :foo "foo foo foo" end ``` ### URL 생성하기 URL을 생성하려면 `url` 헬퍼 메서드를 사용해야 한다. 예를 들어 Haml에서: ```ruby %a{:href => url('/foo')} foo ``` 이것은 리버스 프록시(reverse proxies)와 Rack 라우터를, 만약 존재한다면, 참고한다. This method is also aliased to `to` (see below for an example). 이 메서드는 `to`라는 별칭으로도 사용할 수 있다 (아래 예제 참조). ### 브라우저 재지정(Browser Redirect) `redirect` 헬퍼 메서드를 사용하여 브라우저 리다이렉트를 촉발시킬 수 있다: ```ruby get '/foo' do redirect to('/bar') end ``` 여타 부가적인 매개변수들은 `halt`에서 전달한 인자들처럼 다루어 진다: ```ruby redirect to('/bar'), 303 redirect 'http://google.com', 'wrong place, buddy' ``` `redirect back`을 사용하면 사용자가 왔던 페이지로 다시 돌아가는 리다이렉트도 쉽게 할 수 있다: ```ruby get '/foo' do "
do something" end get '/bar' do do_something redirect back end ``` 리다이렉트와 함께 인자를 전달하려면, 쿼리에 붙이거나: ```ruby redirect to('/bar?sum=42') ``` 또는 세션을 사용하면 된다: ```ruby enable :sessions get '/foo' do session[:secret] = 'foo' redirect to('/bar') end get '/bar' do session[:secret] end ``` ### 캐시 컨트롤(Cache Control) 헤더를 정확하게 설정하는 것은 적절한 HTTP 캐싱의 기본이다. Cache-Control 헤더를 다음과 같이 간단하게 설정할 수 있다: ```ruby get '/' do cache_control :public "cache it!" end ``` 프로 팁: 캐싱은 사전 필터에서 설정하라: ```ruby before do cache_control :public, :must_revalidate, :max_age => 60 end ``` `expires` 헬퍼를 사용하여 그에 상응하는 헤더를 설정한다면, `Cache-Control`이 자동으로 설정될 것이다: ```ruby before do expires 500, :public, :must_revalidate end ``` 캐시를 잘 사용하려면, `etag` 또는 `last_modified`의 사용을 고려해야 할 것이다. 무거운 작업을 하기 *전*에 이들 헬퍼를 호출할 것을 권장하는데, 이러면 만약 클라이언트 캐시에 현재 버전이 이미 들어 있을 경우엔 즉각 응답을 반환(flush)하게 될 것이다: ```ruby get '/article/:id' do @article = Article.find params[:id] last_modified @article.updated_at etag @article.sha1 erb :article end ``` [약한 ETag](http://en.wikipedia.org/wiki/HTTP_ETag#Strong_and_weak_validation)를 사용하는 것도 가능하다: ```ruby etag @article.sha1, :weak ``` 이들 헬퍼는 어떠한 캐싱도 하지 않으며, 대신 필요한 정보를 캐시에 제공한다. 여러분이 만약 손쉬운 리버스 프록시(reverse-proxy) 캐싱 솔루션을 찾고 있다면, [rack-cache](https://github.com/rtomayko/rack-cache)를 써보라: ```ruby require "rack/cache" require "sinatra" use Rack::Cache get '/' do cache_control :public, :max_age => 36000 sleep 5 "hello" end ``` 정적 파일에 `Cache-Control` 헤더 정보를 추가하려면 `:static_cache_control` 설정(아래 참조)을 사용하라: RFC 2616에 따르면 If-Match 또는 If-None-Match 헤더가 `*`로 설정된 경우 요청한 리소스(resource)가 이미 존재하느냐 여부에 따라 다르게 취급해야 한다고 되어 있다. Sinatra는 (get 처럼) 안전하거나 (put 처럼) 멱등인 요청에 대한 리소스는 이미 존재한다고 가정하며, 반면 다른 리소스(예를 들면 post 요청 같은)의 경우는 새 리소스로 취급한다. 이런 설정은 `:new_resource` 옵션으로 전달하여 변경할 수 있다: ```ruby get '/create' do etag '', :new_resource => true Article.create erb :new_article end ``` 여전히 약한 ETag를 사용하고자 한다면, `:kind`으로 전달하자: ```ruby etag '', :new_resource => true, :kind => :weak ``` ### 파일 전송하기(Sending Files) 파일을 전송하려면, `send_file` 헬퍼 메서드를 사용하면 된다: ```ruby get '/' do send_file 'foo.png' end ``` 이 메서드는 몇 가지 옵션을 받는다: ```ruby send_file 'foo.png', :type => :jpg ``` 옵션들:
filename
응답에서의 파일명. 기본값은 실제 파일명이다.
last_modified
Last-Modified 헤더값. 기본값은 파일의 mtime.
type
사용할 컨텐츠 유형. 없으면 파일 확장자로부터 유추된다.
disposition
Content-Disposition에서 사용됨. 가능한 값들: nil (기본값), :attachment:inline
length
Content-Length, 기본값은 파일 크기.
status
전송할 상태 코드. 오류 페이지로 정적 파일을 전송할 경우에 유용.
Rack 핸들러가 지원할 경우, Ruby 프로세스로부터의 스트리밍이 아닌 다른 수단을 사용할 수 있다. 만약 이 헬퍼 메서드를 사용하게 되면, Sinatra는 자동으로 범위 요청(range request)을 처리할 것이다. ### 요청 객체에 접근하기(Accessing the Request Object) 인입되는 요청 객에는 요청 레벨(필터, 라우터, 오류 핸들러)에서 `request` 메서드를 통해 접근 가능하다: ```ruby # 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.scheme # "http" request.script_name # "/example" request.path_info # "/foo" request.port # 80 request.request_method # "GET" request.query_string # "" request.content_length # request.body의 길이 request.media_type # request.body의 미디어 유형 request.host # "example.com" request.get? # true (다른 동사에 대해 유사한 메서드 있음) request.form_data? # false request["SOME_HEADER"] # SOME_HEADER 헤더의 값 request.referrer # 클라이언트의 리퍼러 또는 '/' request.user_agent # 사용자 에이전트 (:agent 조건에서 사용됨) request.cookies # 브라우저 쿠키의 해시 request.xhr? # 이게 ajax 요청인가요? request.url # "http://example.com/example/foo" request.path # "/example/foo" request.ip # 클라이언트 IP 주소 request.secure? # false (ssl 접속인 경우 true) request.forwarded? # true (리버스 프록시 하에서 작동 중이라면) request.env # Rack에 의해 처리되는 로우(raw) env 해시 end ``` 일부 옵션들, `script_name` 또는 `path_info`와 같은 일부 옵션은 쓸 수도 있다: ```ruby before { request.path_info = "/" } get "/" do "all requests end up here" end ``` `request.body`는 IO 또는 StringIO 객체이다: ```ruby post "/api" do request.body.rewind # 누군가 이미 읽은 경우 data = JSON.parse request.body.read "Hello #{data['name']}!" end ``` ### 첨부(Attachments) `attachment` 헬퍼를 사용하여 브라우저에게 응답이 브라우저에 표시되는 게 아니라 디스크에 저장되어야 함을 알릴 수 있다: ```ruby get '/' do attachment "store it!" end ``` 이 때 파일명을 전달할 수도 있다: ```ruby get '/' do attachment "info.txt" "store it!" end ``` ### 날짜와 시간 다루기 Sinatra는 `time_for_` 헬퍼 메서드를 제공하는데, 이 메서드는 주어진 값으로부터 Time 객체를 생성한다. `DateTime` 이나 `Date` 또는 유사한 클래스들도 변환 가능하다: ```ruby get '/' do pass if Time.now > time_for('Dec 23, 2012') "still time" end ``` 이 메서드는 내부적으로 `expires` 나 `last_modified` 같은 곳에서 사용된다. 따라서 여러분은 애플리케이션에서 `time_for`를 오버라이딩하여 이들 메서드의 동작을 쉽게 확장할 수 있다: ```ruby helpers do def time_for(value) case value when :yesterday then Time.now - 24*60*60 when :tomorrow then Time.now + 24*60*60 else super end end end get '/' do last_modified :yesterday expires :tomorrow "hello" end ``` ### 템플릿 파일 참조하기 `find_template`는 렌더링할 템플릿 파일을 찾는데 사용된다: ```ruby find_template settings.views, 'foo', Tilt[:haml] do |file| puts "could be #{file}" end ``` This is not really useful. But it is useful that you can actually override this method to hook in your own lookup mechanism. For instance, if you want to be able to use more than one view directory: 이건 별로 유용하지 않다. 그렇지만 이 메서드를 오버라이드하여 여러분만의 참조 메커니즘에서 가로채는 것은 유용하다. 예를 들어, 하나 이상의 뷰 디렉터리를 사용하고자 한다면: ```ruby set :views, ['views', 'templates'] helpers do def find_template(views, name, engine, &block) Array(views).each { |v| super(v, name, engine, &block) } end end ``` 또다른 예제는 각각의 엔진마다 다른 디렉터리를 사용할 경우다: ```ruby set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views' helpers do def find_template(views, name, engine, &block) _, folder = views.detect { |k,v| engine == Tilt[k] } folder ||= views[:default] super(folder, name, engine, &block) end end ``` 여러분은 이것을 간단하게 확장(extension)으로 만들어 다른 사람들과 공유할 수 있다! `find_template`은 그 파일이 실제 존재하는지 검사하지 않음에 유의하자. 대신 모든 가능한 경로에 대해 주어진 블록을 호출할 뿐이다. 이것은 성능 문제는 아닌 것이, `render`는 파일이 발견되는 즉시 `break`를 사용할 것이기 때문이다. 또한, 템플릿 위치(그리고 콘텐츠)는 개발 모드에서 실행 중이 아니라면 캐시될 것이다. 정말로 멋진 메세드를 작성하고 싶다면 이 점을 명심하자. ## 설정(Configuration) 모든 환경에서, 시작될 때, 한번만 실행: ```ruby configure do # 옵션 하나 설정 set :option, 'value' # 여러 옵션 설정 set :a => 1, :b => 2 # `set :option, true`와 동일 enable :option # `set :option, false`와 동일 disable :option # 블록으로 동적인 설정을 할 수도 있음 set(:css_dir) { File.join(views, 'css') } end ``` 환경(RACK_ENV 환경 변수)이 `:production`일 때만 실행: ```ruby configure :production do ... end ``` 환경이 `:production` 또는 `:test`일 때 실행: ```ruby configure :production, :test do ... end ``` 이들 옵션은 `settings`를 통해 접근 가능하다: ```ruby configure do set :foo, 'bar' end get '/' do settings.foo? # => true settings.foo # => 'bar' ... end ``` ### 공격 방어 설정하기(Configuring attack protection) Sinatra는 [Rack::Protection](https://github.com/rkh/rack-protection#readme)을 사용하여 일반적인, 일어날 수 있는 공격에 대비한다. 이 부분은 간단하게 비활성시킬 수 있다(성능 향상 효과를 가져올 것이다): ```ruby disable :protection ``` 하나의 방어층만 스킵하려면, 옵션 해시에 `protection`을 설정하면 된다: ```ruby set :protection, :except => :path_traversal ``` 방어막 여러 개를 비활성하려면, 배열로 주면 된다: ```ruby set :protection, :except => [:path_traversal, :session_hijacking] ``` ### 가능한 설정들(Available Settings)
absolute_redirects
만약 비활성이면, Sinatra는 상대경로 리다이렉트를 허용할 것이지만, 이렇게 되면 Sinatra는 더 이상 오직 절대경로 리다이렉트만 허용하고 있는 RFC 2616(HTTP 1.1)에 위배될 것이다. 적정하게 설정되지 않은 리버스 프록시 하에서 앱을 실행 중이라면 활성화시킬 것. rul 헬퍼는, 만약 두 번째 매개변수로 false를 전달하지만 않는다면, 여전히 절대경로 URL을 생성할 것임에 유의하자. 기본값은 비활성.
add_charsets
content_type가 문자셋 정보에 자동으로 추가하게 될 마임(mime) 타입. 이 옵션은 오버라이딩하지 말고 추가해야 한다: settings.add_charsets << "application/foobar"
app_file
메인 애플리케이션 파일의 경로. 프로젝트 루트와 뷰, 그리고 public 폴더, 인라인 템플릿을 파악할 때 사용됨.
bind
바인드할 IP 주소(기본값: 0.0.0.0). 오직 빌트인(built-in) 서버에서만 사용됨.
default_encoding
모를 때 가정할 인코딩 (기본값은 "utf-8").
dump_errors
로그로 에러 출력.
environment
현재 환경, 기본값은 ENV['RACK_ENV'] 또는 알 수 없을 경우 "development".
logging
로거(logger) 사용.
lock
매 요청에 걸쳐 잠금(lock)을 설정. Ruby 프로세스 당 요청을 동시에 할 경우. 앱이 스레드 안전(thread-safe)이 아니라면 활성화시킬 것. 기본값은 비활성.
method_override
put/delete를 지원하지 않는 브라우저에서 put/delete 폼을 허용하는 _method 꼼수 사용.
port
접속 포트. 빌트인 서버에서만 사용됨.
prefixed_redirects
절대경로가 주어지지 않은 리다이렉트에 request.script_name를 삽입할지 여부. 이렇게 하면 redirect '/foo'redirect to('/foo') 처럼 동작. 기본값은 비활성.
protection
웹 공격 방어를 활성화시킬 건지 여부. 위의 보안 섹션 참조.
public_folder
public 파일이 제공될 폴더의 경로. static 파일 제공이 활성화된 경우만 사용됨(아래 static참조). 만약 설정이 없으면 app_file로부터 유추됨.
reload_templates
요청 간에 템플릿을 리로드(reload)할 건지 여부. 개발 모드에서는 활성됨.
root
프로젝트 루트 디렉터리 경로. 설정이 없으면 app_file 설정으로부터 유추됨.
raise_errors
예외 발생(애플리케이션은 중단됨). 기본값은 environment"test"인 경우는 활성, 그렇지 않으면 비활성.
run
활성화되면, Sinatra가 웹서버의 시작을 핸들링. rackup 또는 다른 도구를 사용하는 경우라면 활성화시키지 말 것.
running
빌트인 서버가 실행 중인지? 이 설정은 변경하지 말 것!
server
빌트인 서버로 사용할 서버 또는 서버 목록. 기본값은 ['thin', 'mongrel', 'webrick']이며 순서는 우선순위를 의미.
sessions
Rack::Session::Cookie를 사용한 쿠키 기반 세션 활성화. 보다 자세한 정보는 '세션 사용하기' 참조.
show_exceptions
예외 발생 시에 브라우저에 스택 추적을 보임. 기본값은 environment"development"인 경우는 활성, 나머지는 비활성.
static
Sinatra가 정적(static) 파일을 핸들링할 지 여부. 이 기능을 수행하는 서버를 사용하는 경우라면 비활성시킬 것. 비활성시키면 성능이 올라감. 기본값은 전통적 방식에서는 활성, 모듈 앱에서는 비활성.
static_cache_control
Sinatra가 정적 파일을 제공하는 경우, 응답에 Cache-Control 헤더를 추가할 때 설정. cache_control 헬퍼를 사용. 기본값은 비활성. 여러 값을 설정할 경우는 명시적으로 배열을 사용할 것: set :static_cache_control, [:public, :max_age => 300]
threaded
true로 설정하면, Thin이 요청을 처리하는데 있어 EventMachine.defer를 사용하도록 함.
views
뷰 폴더 경로. 설정하지 않은 경우 app_file로부터 유추됨.
## 환경(Environments) 환경은 `RACK_ENV` 환경 변수를 통해서도 설정할 수 있다. 기본값은 "development"다. 이 모드에서, 모든 템플릿들은 요청 간에 리로드된다. 특별한 `not_found` 와 `error` 핸들러가 이 환경에 설치되기 때문에 브라우저에서 스택 추적을 볼 수 있을 것이다. `"production"`과 `"test"`에서는 템플릿은 캐시되는 게 기본값이다. 다른 환경으로 실행시키려면 `-e`옵션을 사용하면 된다: ```ruby ruby my_app.rb -e [ENVIRONMENT] ``` 현재 설정된 환경이 무엇인지 검사하기 위해 사전 정의된 `development?`, `test?` 및 `production?` 메서드를 사용할 수 있다. ## 예외 처리(Error Handling) 예외 핸들러는 라우터 및 사전 필터와 동일한 맥락에서 실행된다. 이 말인즉, 이들이 제공하는 모든 것들을 사용할 수 있다는 말이다. 예를 들면 `haml`, `erb`, `halt`, 등등. ### 찾을 수 없음(Not Found) `Sinatra::NotFound` 예외가 발생하거나 또는 응답의 상태 코드가 404라면, `not_found` 핸들러가 호출된다: ```ruby not_found do '아무 곳에도 찾을 수 없습니다.' end ``` ### 오류(Error) `error` 핸들러는 라우터 또는 필터에서 뭐든 오류가 발생할 경우에 호출된다. 예외 객체는 Rack 변수 `sinatra.error`로부터 얻을 수 있다: ```ruby error do '고약한 오류가 발생했군요 - ' + env['sinatra.error'].name end ``` 사용자 정의 오류: ```ruby error MyCustomError do '무슨 일이 생겼나면요...' + env['sinatra.error'].message end ``` 그런 다음, 이 오류가 발생하면: ```ruby get '/' do raise MyCustomError, '안좋은 일' end ``` 다음을 얻는다: ```ruby 무슨 일이 생겼냐면요... 안좋은 일 ``` 또는, 상태 코드에 대해 오류 핸들러를 설치할 수 있다: ```ruby error 403 do '액세스가 금지됨' end get '/secret' do 403 end ``` Or a range: ```ruby error 400..510 do '어이쿠' end ``` Sinatra는 개발 환경에서 동작할 경우에 특별한 `not_found` 와 `error` 핸들러를 설치한다. ## Rack 미들웨어(Rack Middleware) Sinatra는 [Rack](http://rack.rubyforge.org/) 위에서 동작하며, Rack은 루비 웹 프레임워크를 위한 최소한의 표준 인터페이스이다. Rack이 애플리케이션 개발자들에게 제공하는 가장 흥미로운 기능 중 하나가 바로 "미들웨어(middleware)"에 대한 지원이며, 여기서 미들웨어란 서버와 여러분의 애플리케이션 사이에 위치하면서 HTTP 요청/응답을 모니터링하거나/또는 조작함으로써 다양한 유형의 공통 기능을 제공하는 컴포넌트(component)다. Sinatra는 톱레벨의 `use` 메서드를 사용하여 Rack 미들웨어의 파이프라인을 만드는 일을 식은 죽 먹기로 만든다: ```ruby require 'sinatra' require 'my_custom_middleware' use Rack::Lint use MyCustomMiddleware get '/hello' do 'Hello World' end ``` `use`의 의미는 [Rack::Builder](http://rack.rubyforge.org/doc/classes/Rack/Builder.html]) DSL (rackup 파일에서 가장 많이 사용된다)에서 정의한 것들과 동일하다. 예를 들어, `use` 메서드는 블록 뿐 아니라 여러 개의/가변적인 인자도 받는다: ```ruby use Rack::Auth::Basic do |username, password| username == 'admin' && password == 'secret' end ``` Rack은 로깅, 디버깅, URL 라우팅, 인증, 그리고 세센 핸들링을 위한 다양한 표준 미들웨어로 분산되어 있다. Sinatra는 설정에 기반하여 이들 컴포넌트들 중 많은 것들을 자동으로 사용하며, 따라서 여러분은 일반적으로는 `use`를 명시적으로 사용할 필요가 없을 것이다. 유용한 미들웨어들은 [rack](https://github.com/rack/rack/tree/master/lib/rack), [rack-contrib](https://github.com/rack/rack-contrib#readme), [CodeRack](http://coderack.org/) 또는 [Rack wiki](https://github.com/rack/rack/wiki/List-of-Middleware) 에서 찾을 수 있다. ## 테스팅(Testing) Sinatra 테스트는 Rack 기반 어떠한 테스팅 라이브러리 또는 프레임워크를 사용하여도 작성할 수 있다. [Rack::Test](http://rdoc.info/github/brynary/rack-test/master/frames)를 권장한다: ```ruby require 'my_sinatra_app' require 'test/unit' require 'rack/test' class MyAppTest < Test::Unit::TestCase include Rack::Test::Methods def app Sinatra::Application end def test_my_default get '/' assert_equal 'Hello World!', last_response.body end def test_with_params get '/meet', :name => 'Frank' assert_equal 'Hello Frank!', last_response.body end def test_with_rack_env get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' assert_equal "You're using Songbird!", last_response.body end end ``` ## Sinatra::Base - 미들웨어(Middleware), 라이브러리(Libraries), 그리고 모듈 앱(Modular Apps) 톱레벨에서 앱을 정의하는 것은 마이크로 앱(micro-app) 수준에서는 잘 동작하지만, Rack 미들웨어나, Rails 메탈(metal) 또는 서버 컴포넌트를 갖는 간단한 라이브러리, 또는 더 나아가 Sinatra 익스텐션(extension) 같은 재사용 가능한 컴포넌트들을 구축할 경우에는 심각한 약점을 가진다. 톱레벨은 마이크로 앱 스타일의 설정을 가정한다(즉, 하나의 단일 애플리케이션 파일과 `./public` 및 `./views` 디렉터리, 로깅, 예외 상세 페이지 등등). 이게 바로 `Sinatra::Base`가 필요한 부분이다: ```ruby require 'sinatra/base' class MyApp < Sinatra::Base set :sessions, true set :foo, 'bar' get '/' do 'Hello world!' end end ``` `Sinatra::Base` 서브클래스에서 사용가능한 메서드들은 톱레벨 DSL로 접근 가능한 것들과 동일하다. 대부분의 톱레벨 앱들이 다음 두 가지만 수정하면 `Sinatra::Base` 컴포넌트로 변환 가능하다: * 파일은 `sinatra`가 아닌 `sinatra/base`를 require해야 하며, 그렇지 않으면 모든 Sinatra의 DSL 메서드들이 메인 네임스페이스에 불러지게 된다. * 앱의 라우터, 예외 핸들러, 필터, 그리고 옵션들을 `Sinatra::Base`의 서브클래스에 둘 것. `Sinatra::Base`는 빈서판(blank slate)이다. 빌트인 서버를 비롯한 대부분의 옵션들이 기본값으로 꺼져 있다. 가능한 옵션들과 그 작동에 대한 상세는 [Options and Configuration](http://sinatra.github.com/configuration.html)을 참조할 것. ### 모듈(Modular) vs. 전통적 방식(Classic Style) 일반적인 믿음과는 반대로, 전통적 방식에 잘못된 부분은 없다. 여러분 애플리케이션에 맞다면, 모듈 애플리케이션으로 전환할 필요는 없다. 모듈 방식이 아닌 전통적 방식을 사용할 경우 생기는 주된 단점은 루비 프로세스 당 오직 하나의 Sinatra 애플리케이션만 사용할 수 있다는 점이다. 만약 하나 이상을 사용할 계획이라면, 모듈 방식으로 전환하라. 모듈 방식과 전통적 방식을 섞어쓰지 못할 이유는 없다. 하나의 방식에서 다른 것으로 전환할 경우에는, 기본값 설정의 미묘한 차이에 유의해야 한다: 설정전통적 방식 모듈 방식
Setting Classic Modular
app_file sinatra를 로딩하는 파일 Sinatra::Base를 서브클래싱한 파일
run $0 == app_file false
logging true false
method_override true false
inline_templates true false
static true false
### 모듈 애플리케이션(Modular Application) 제공하기 모듈 앱을 시작하는 두 가지 일반적인 옵션이 있는데, 공격적으로 `run!`으로 시작하거나: ```ruby # my_app.rb require 'sinatra/base' class MyApp < Sinatra::Base # ... 여기에 앱 코드가 온다 ... # 루비 파일이 직접 실행될 경우에 서버를 시작 run! if app_file == $0 end ``` 다음과 같이 시작: ```ruby ruby my_app.rb ``` 또는 `config.ru`와 함께 사용하며, 이 경우는 어떠한 Rack 핸들러라도 사용할 수 있다: ```ruby # config.ru require './my_app' run MyApp ``` 실행: ```ruby rackup -p 4567 ``` ### config.ru로 전통적 방식의 애플리케이션 사용하기 앱 파일을 다음과 같이 작성하고: ```ruby # app.rb require 'sinatra' get '/' do 'Hello world!' end ``` 대응하는 `config.ru`는 다음과 같이 작성: ```ruby require './app' run Sinatra::Application ``` ### 언제 config.ru를 사용할까? Good signs you probably want to use a `config.ru`: 다음은 `config.ru`를 사용하게 될 징후들이다: * 다른 Rack 핸들러(Passenger, Unicorn, Heroku, ...)로 배포하고자 할 때. * 하나 이상의 `Sinatra::Base` 서브클래스를 사용하고자 할 때. * Sinatra를 최종점(endpoint)이 아니라, 오로지 미들웨어로만 사용하고자 할 때. **모듈 방식으로 전환했다는 이유만으로 `config.ru`로 전환할 필요는 없으며, 또한 `config.ru`를 사용한다고 해서 모듈 방식을 사용해야 하는 것도 아니다.** ### Sinatra를 미들웨어로 사용하기 Sinatra에서 다른 Rack 미들웨어를 사용할 수 있을 뿐 아니라, 모든 Sinatra 애플리케이션은 순차로 어떠한 Rack 종착점 앞에 미들웨어로 추가될 수 있다. 이 종착점은 다른 Sinatra 애플리케이션이 될 수도 있고, 또는 Rack 기반의 어떠한 애플리케이션(Rails/Ramaze/Camping/...)이라도 가능하다: ```ruby require 'sinatra/base' class LoginScreen < Sinatra::Base enable :sessions get('/login') { haml :login } post('/login') do if params[:name] == 'admin' && params[:password] == 'admin' session['user_name'] = params[:name] else redirect '/login' end end end class MyApp < Sinatra::Base # 미들웨어는 사전 필터보다 앞서 실행됨 use LoginScreen before do unless session['user_name'] halt "접근 거부됨, 로그인 하세요." end end get('/') { "Hello #{session['user_name']}." } end ``` ### 동적인 애플리케이션 생성(Dynamic Application Creation) 경우에 따라선 어떤 상수에 할당하지 않고 런타임에서 새 애플리케이션들을 생성하고 싶을 수도 있을 것인데, 이 때는 `Sinatra.new`를 쓰면 된다: ```ruby require 'sinatra/base' my_app = Sinatra.new { get('/') { "hi" } } my_app.run! ``` 이것은 선택적 인자로 상속할 애플리케이션을 받는다: ```ruby # config.ru require 'sinatra/base' controller = Sinatra.new do enable :logging helpers MyHelpers end map('/a') do run Sinatra.new(controller) { get('/') { 'a' } } end map('/b') do run Sinatra.new(controller) { get('/') { 'b' } } end ``` 이것은 Sintra 익스텐션을 테스팅하거나 또는 여러분의 라이브러리에서 Sinatra를 사용할 경우에 특히 유용하다. 또한 이 방법은 Sinatra를 미들웨어로 사용하는 것을 아주 쉽게 만들어 준다: ```ruby require 'sinatra/base' use Sinatra do get('/') { ... } end run RailsProject::Application ``` ## 범위(Scopes)와 바인딩(Binding) 현재 어느 범위에 있느냐가 어떤 메서드와 변수를 사용할 수 있는지를 결정한다. ### 애플리케이션/클래스 범위 모든 Sinatra 애플리케이션은 `Sinatra::Base`의 서브클래스에 대응된다. 만약 톱레벨 DSL (`require 'sinatra'`)을 사용한다면, 이 클래스는 `Sinatra::Application`이며, 그렇지 않을 경우라면 여러분이 명시적으로 생성한 그 서브클래스가 된다. 클래스 레벨에서는 `get` 이나 `before` 같은 메서드들을 가지나, `request` 객체나 `session` 에는 접근할 수 없다. 왜냐면 모든 요청에 대해 애플리케이션 클래스는 오직 하나이기 때문이다. `set`으로 생성한 옵션들은 클래스 레벨의 메서드들이다: ```ruby class MyApp < Sinatra::Base # 이봐요, 저는 애플리케이션 범위에 있다구요! set :foo, 42 foo # => 42 get '/foo' do # 저기요, 전 이제 더 이상 애플리케이션 범위 속에 있지 않아요! end end ``` 다음 속에 있을 때 애플리케이션 범위가 된다: * 애플리케이션 클래스 본문 * 확장으로 정의된 메서드 * `helpers`로 전달된 블록 * `set`의 값으로 사용된 Procs/blocks * `Sinatra.new`로 전달된 블록 범위 객체 (클래스)는 다음과 같이 접근할 수 있다: * configure 블록으로 전달된 객체를 통해(`configure { |c| ... }`) * 요청 범위 내에서 `settings` ### 요청/인스턴스 범위 매 요청마다, 애플리케이션 클래스의 새 인스턴스가 생성되고 모든 핸들러 블록은 그 범위 내에서 실행된다. 이 범위 내에서 여러분은 `request` 와 `session` 객체에 접근하거나 `erb` 나 `haml` 같은 렌더링 메서드를 호출할 수 있다. 요청 범위 내에서 애플리케이션 범위는 `settings` 헬퍼를 통해 접근 가능하다: ```ruby class MyApp < Sinatra::Base # 이봐요, 전 애플리케이션 범위에 있다구요! get '/define_route/:name' do # '/define_route/:name'의 요청 범위 @value = 42 settings.get("/#{params[:name]}") do # "/#{params[:name]}"의 요청 범위 @value # => nil (동일한 요청이 아님) end "라우터가 정의됨!" end end ``` 다음 속에 있을 때 요청 범위 바인딩이 된다: * get/head/post/put/delete/options 블록 * before/after 필터 * 헬퍼(helper) 메서드 * 템플릿/뷰 ### 위임 범위(Delegation Scope) 위임 범위(delegation scope)는 메서드를 단순히 클래스 범위로 보낸다(forward). 그렇지만, 100% 클래스 범위처럼 움직이진 않는데, 왜냐면 클래스 바인딩을 갖지 않기 때문이다. 오직 명시적으로 위임(delegation) 표시된 메서드들만 사용 가능하며 또한 클래스 범위와 변수/상태를 공유하지 않는다 (유의: `self`가 다르다). `Sinatra::Delegator.delegate :method_name`을 호출하여 메서드 위임을 명시적으로 추가할 수 있다. 다음 속에 있을 때 위임 범위 바인딩을 갖는다: * 톱레벨 바인딩, `require "sinatra"`를 한 경우 * `Sinatra::Delegator` 믹스인으로 확장된 객체 직접 코드를 살펴보길 바란다: [Sinatra::Delegator 믹스인](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/base.rb#L1609-1633) 코드는 [메인 객체를 확장한 것](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/main.rb#L28-30)이다. ## 명령행(Command Line) Sinatra 애플리케이션은 직접 실행할 수 있다: ```ruby ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER] ``` 옵션들: ``` -h # 도움말 -p # 포트 설정 (기본값은 4567) -o # 호스트 설정 (기본값은 0.0.0.0) -e # 환경 설정 (기본값은 development) -s # rack 서버/핸들러 지정 (기본값은 thin) -x # mutex 잠금 켜기 (기본값은 off) ``` ## 요구사항(Requirement) 다음의 루비 버전은 공식적으로 지원한다:
Ruby 1.8.7
1.8.7은 완전하게 지원되지만, 꼭 그래야할 특별한 이유가 없다면, 1.9.2로 업그레이드하거나 또는 JRuby나 Rubinius로 전환할 것을 권장한다. 1.8.7에 대한 지원은 Sinatra 2.0과 Ruby 2.0 이전에는 중단되지 않을 것이다. 또한 그때도, 우리는 계속 지원할 것이다. Ruby 1.8.6은 더이상 지원되지 않는다. 만약 1.8.6으로 실행하려 한다면, Sinatra 1.2로 다운그레이드하라. Sinatra 1.4.0이 릴리스될 때 까지는 버그 픽스를 받을 수 있을 것이다.
Ruby 1.9.2
1.9.2는 완전하게 지원되면 권장된다. Radius와 Maraby는 현재 1.9와 호환되지 않음에 유의하라. 1.9.2p0은, Sinatra를 실행했을 때 세그먼트 오류가 발생한다고 알려져 있으니 사용하지 말라. Ruby 1.9.4/2.0 릴리스까지는 적어도 지원을 계속할 것이며, 최신 1.9 릴리스에 대한 지원은 Ruby 코어팀이 지원하고 있는 한 계속 지원할 것이다.
Ruby 1.9.3
1.9.3은 완전하게 지원된다. 그렇지만 프로덕션에서의 사용은 보다 상위의 패치 레벨이 릴리스될 때까지 기다리길 권장한다(현재는 p0). 이전 버전에서 1.9.3으로 전환할 경우 모든 세션이 무효화된다는 점을 유의하라.
Rubinius
Rubinius는 공식적으로 지원되며 (Rubinius >= 1.2.4), 모든 템플릿 언어를 포함한 모든 것들이 작동한다. 조만간 출시될 2.0 릴리스 역시 지원할 것이다.
JRuby
JRuby는 공식적으로 지원된다 (JRuby >= 1.6.5). 서드 파티 템플릿 라이브러리와의 문제는 알려진 바 없지만, 만약 JRuby를 사용하기로 했다면, JRuby rack 핸들러를 찾아보길 바란다. Thin 웹 서버는 JRuby에서 완전하게 지원되지 않는다. JRuby의 C 확장 지원은 아직 실험 단계이며, RDiscount, Redcarpet 및 RedCloth가 현재 이 영향을 받는다.
또한 우리는 새로 나오는 루비 버전을 주시한다. 다음 루비 구현체들은 공식적으로 지원하지 않지만 여전히 Sinatra를 실행할 수 있는 것으로 알려져 있다: * JRuby와 Rubinius 예전 버전 * Ruby Enterprise Edition * MacRuby, Maglev, IronRuby * Ruby 1.9.0 및 1.9.1 (그러나 이 버전들은 사용하지 말 것을 권함) 공식적으로 지원하지 않는다는 것의 의미는 무언가가 그쪽에서만 잘못되고 지원되는 플랫폼에서는 그러지 않을 경우, 우리의 문제가 아니라 그쪽의 문제로 간주한다는 뜻이다. 또한 우리는 CI를 ruby-head (곧 나올 2.0.0) 과 1.9.4 브랜치에 맞춰 실행하지만, 계속해서 변하고 있기 때문에 아무 것도 보장할 수는 없다. 1.9.4p0와 2.0.0p0가 지원되길 기대한다. Sinatra는 선택한 루비 구현체가 지원하는 어떠한 운영체제에서도 작동해야 한다. 현재 Cardinal, SmallRuby, BlueRuby 또는 1.8.7 이전의 루비 버전에서는 Sinatra를 실행할 수 없을 것이다. ## 최신(The Bleeding Edge) Sinatra의 가장 최근 코드를 사용하고자 한다면, 여러분 애플리케이션을 마스터 브랜치에 맞춰 실행하면 되지만, 덜 안정적일 것임에 분명하다. 또한 우리는 가끔 사전배포(prerelease) 젬을 푸시하기 때문에, 다음과 같이 할 수 있다 ```ruby gem install sinatra --pre ``` 최신 기능들을 얻기 위해선 ### Bundler를 사용하여 여러분 애플리케이션을 최신 Sinatra로 실행하고자 한다면, [Bundler](http://gembundler.com/)를 사용할 것을 권장한다. 우선, 아직 설치하지 않았다면 bundler를 설치한다: ```ruby gem install bundler ``` 그런 다음, 프로젝트 디렉터리에서, `Gemfile`을 하나 만든다: ```ruby source :rubygems gem 'sinatra', :git => "git://github.com/sinatra/sinatra.git" # 다른 의존관계들 gem 'haml'# 예를 들어, haml을 사용한다면 gem 'activerecord', '~> 3.0' # 아마도 ActiveRecord 3.x도 필요할 것 ``` 이 속에 애플리케이션의 모든 의존관계를 나열해야 함에 유의하자. 그렇지만, Sinatra가 직접적인 의존관계에 있는 것들 (Rack과 Tilt)은 Bundler가 자동으로 추출하여 추가할 것이다. 이제 여러분은 다음과 같이 앱을 실행할 수 있다: ```ruby bundle exec ruby myapp.rb ``` ### 직접 하기(Roll Your Own) 로컬 클론(clone)을 생성한 다음 `$LOAD_PATH`에 `sinatra/lib` 디렉터리를 주고 여러분 앱을 실행한다: ```ruby cd myapp git clone git://github.com/sinatra/sinatra.git ruby -Isinatra/lib myapp.rb ``` 이후에 Sinatra 소스를 업데이트하려면: ```ruby cd myapp/sinatra git pull ``` ### 전역으로 설치(Install Globally) 젬을 직접 빌드할 수 있다: ```ruby git clone git://github.com/sinatra/sinatra.git cd sinatra rake sinatra.gemspec rake install ``` 만약 젬을 루트로 설치한다면, 마지막 단계는 다음과 같이 해야 한다 ```ruby sudo rake install ``` ## 버저닝(Versioning) Sinatra는 [시맨틱 버저닝Semantic Versioning](http://semver.org/)을 준수한다. SemVer 및 SemVerTag 둘 다 해당된. ## 더 읽을 거리(Further Reading) * [프로젝트 웹사이트](http://www.sinatrarb.com/) - 추가 문서들, 뉴스, 그리고 다른 리소스들에 대한 링크. * [기여하기](http://www.sinatrarb.com/contributing) - 버그를 찾았나요? 도움이 필요한가요? 패치를 하셨나요? * [이슈 트래커](http://github.com/sinatra/sinatra/issues) * [트위터](http://twitter.com/sinatra) * [Mailing List](http://groups.google.com/group/sinatrarb/topics) * [IRC: #sinatra](irc://chat.freenode.net/#sinatra) http://freenode.net * [Sinatra Book](http://sinatra-book.gittr.com) Cookbook 튜토리얼 * [Sinatra Recipes](http://recipes.sinatrarb.com/) 커뮤니티가 만드는 레시피 * http://rubydoc.info에 있는 [최종 릴리스](http://rubydoc.info/gems/sinatra) 또는 [current HEAD](http://rubydoc.info/github/sinatra/sinatra)에 대한 API 문서 * [CI server](http://travis-ci.org/sinatra/sinatra) sinatra-1.4.3/examples/0000755000004100000410000000000012161612727015033 5ustar www-datawww-datasinatra-1.4.3/examples/stream.ru0000644000004100000410000000106412161612727016677 0ustar www-datawww-data# this example does *not* work properly with WEBrick # # run *one* of these: # # rackup -s mongrel stream.ru # gem install mongrel # thin -R stream.ru start # gem install thin # unicorn stream.ru # gem install unicorn # puma stream.ru # gem install puma require 'sinatra/base' class Stream < Sinatra::Base get '/' do content_type :txt 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 end run Stream sinatra-1.4.3/examples/simple.rb0000755000004100000410000000013312161612727016651 0ustar www-datawww-data#!/usr/bin/env ruby -I ../lib -I lib require 'sinatra' get('/') { 'this is a simple app' } sinatra-1.4.3/examples/chat.rb0000755000004100000410000000245712161612727016312 0ustar www-datawww-data#!/usr/bin/env ruby -I ../lib -I lib # coding: utf-8 require 'sinatra' set :server, 'thin' connections = [] get '/' do halt erb(:login) unless params[:user] erb :chat, :locals => { :user => params[:user].gsub(/\W/, '') } end get '/stream', :provides => 'text/event-stream' do stream :keep_open do |out| connections << out out.callback { connections.delete(out) } end end post '/' do connections.each { |out| out << "data: #{params[:msg]}\n\n" } 204 # response without entity body end __END__ @@ layout Super Simple Chat with Sinatra <%= yield %> @@ login
@@ chat

sinatra-1.4.3/LICENSE0000644000004100000410000000207112161612727014222 0ustar www-datawww-dataCopyright (c) 2007, 2008, 2009, 2010, 2011 Blake Mizerany Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.sinatra-1.4.3/README.md0000644000004100000410000020754712161612727014513 0ustar www-datawww-data# Sinatra Sinatra is a [DSL](http://en.wikipedia.org/wiki/Domain-specific_language) for quickly creating web applications in Ruby with minimal effort: ``` ruby # myapp.rb require 'sinatra' get '/' do 'Hello world!' end ``` Install the gem: ``` shell gem install sinatra ``` And run with: ``` shell ruby myapp.rb ``` View at: http://localhost:4567 It is recommended to also run `gem install thin`, which Sinatra will pick up if available. ## Table of Contents * [Sinatra](#sinatra) * [Table of Contents](#table-of-contents) * [Routes](#routes) * [Conditions](#conditions) * [Return Values](#return-values) * [Custom Route Matchers](#custom-route-matchers) * [Static Files](#static-files) * [Views / Templates](#views--templates) * [Literal Templates](#literal-templates) * [Available Template Languages](#available-template-languages) * [Haml Templates](#haml-templates) * [Erb Templates](#erb-templates) * [Builder Templates](#builder-templates) * [Nokogiri Templates](#nokogiri-templates) * [Sass Templates](#sass-templates) * [SCSS Templates](#scss-templates) * [Less Templates](#less-templates) * [Liquid Templates](#liquid-templates) * [Markdown Templates](#markdown-templates) * [Textile Templates](#textile-templates) * [RDoc Templates](#rdoc-templates) * [Radius Templates](#radius-templates) * [Markaby Templates](#markaby-templates) * [RABL Templates](#rabl-templates) * [Slim Templates](#slim-templates) * [Creole Templates](#creole-templates) * [CoffeeScript Templates](#coffeescript-templates) * [Stylus Templates](#stylus-templates) * [Yajl Templates](#yajl-templates) * [WLang Templates](#wlang-templates) * [Accessing Variables in Templates](#accessing-variables-in-templates) * [Templates with `yield` and nested layouts](#templates-with-yield-and-nested-layouts) * [Inline Templates](#inline-templates) * [Named Templates](#named-templates) * [Associating File Extensions](#associating-file-extensions) * [Adding Your Own Template Engine](#adding-your-own-template-engine) * [Filters](#filters) * [Helpers](#helpers) * [Using Sessions](#using-sessions) * [Halting](#halting) * [Passing](#passing) * [Triggering Another Route](#triggering-another-route) * [Setting Body, Status Code and Headers](#setting-body-status-code-and-headers) * [Streaming Responses](#streaming-responses) * [Logging](#logging) * [Mime Types](#mime-types) * [Generating URLs](#generating-urls) * [Browser Redirect](#browser-redirect) * [Cache Control](#cache-control) * [Sending Files](#sending-files) * [Accessing the Request Object](#accessing-the-request-object) * [Attachments](#attachments) * [Dealing with Date and Time](#dealing-with-date-and-time) * [Looking Up Template Files](#looking-up-template-files) * [Configuration](#configuration) * [Configuring attack protection](#configuring-attack-protection) * [Available Settings](#available-settings) * [Environments](#environments) * [Error Handling](#error-handling) * [Not Found](#not-found) * [Error](#error) * [Rack Middleware](#rack-middleware) * [Testing](#testing) * [Sinatra::Base - Middleware, Libraries, and Modular Apps](#sinatrabase---middleware-libraries-and-modular-apps) * [Modular vs. Classic Style](#modular-vs-classic-style) * [Serving a Modular Application](#serving-a-modular-application) * [Using a Classic Style Application with a config.ru](#using-a-classic-style-application-with-a-configru) * [When to use a config.ru?](#when-to-use-a-configru) * [Using Sinatra as Middleware](#using-sinatra-as-middleware) * [Dynamic Application Creation](#dynamic-application-creation) * [Scopes and Binding](#scopes-and-binding) * [Application/Class Scope](#applicationclass-scope) * [Request/Instance Scope](#requestinstance-scope) * [Delegation Scope](#delegation-scope) * [Command Line](#command-line) * [Requirement](#requirement) * [The Bleeding Edge](#the-bleeding-edge) * [With Bundler](#with-bundler) * [Roll Your Own](#roll-your-own) * [Install Globally](#install-globally) * [Versioning](#versioning) * [Further Reading](#further-reading) ## Routes In Sinatra, a route is an HTTP method paired with a URL-matching pattern. Each route is associated with a block: ``` ruby get '/' do .. show something .. end post '/' do .. create something .. end put '/' do .. replace something .. end patch '/' do .. modify something .. end delete '/' do .. annihilate something .. end options '/' do .. appease something .. end link '/' do .. affiliate something .. end unlink '/' do .. separate something .. end ``` Routes are matched in the order they are defined. The first route that matches the request is invoked. Route patterns may include named parameters, accessible via the `params` hash: ``` ruby get '/hello/:name' do # matches "GET /hello/foo" and "GET /hello/bar" # params[:name] is 'foo' or 'bar' "Hello #{params[:name]}!" end ``` You can also access named parameters via block parameters: ``` ruby get '/hello/:name' do |n| # matches "GET /hello/foo" and "GET /hello/bar" # params[:name] is 'foo' or 'bar' # n stores params[:name] "Hello #{n}!" end ``` Route patterns may also include splat (or wildcard) parameters, accessible via the `params[:splat]` array: ``` ruby get '/say/*/to/*' do # matches /say/hello/to/world params[:splat] # => ["hello", "world"] end get '/download/*.*' do # matches /download/path/to/file.xml params[:splat] # => ["path/to/file", "xml"] end ``` Or with block parameters: ``` ruby get '/download/*.*' do |path, ext| [path, ext] # => ["path/to/file", "xml"] end ``` Route matching with Regular Expressions: ``` ruby get %r{/hello/([\w]+)} do "Hello, #{params[:captures].first}!" end ``` Or with a block parameter: ``` ruby get %r{/hello/([\w]+)} do |c| "Hello, #{c}!" end ``` Route patterns may have optional parameters: ``` ruby 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: ``` ruby get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do "You're using Songbird version #{params[:agent][0]}" end get '/foo' do # Matches non-songbird browsers end ``` Other available conditions are `host_name` and `provides`: ``` ruby get '/', :host_name => /^admin\./ do "Admin Area, Access denied!" end get '/', :provides => 'html' do haml :index end get '/', :provides => ['rss', 'atom', 'xml'] do builder :feed end ``` You can easily define your own conditions: ``` ruby set(:probability) { |value| condition { rand <= value } } get '/win_a_car', :probability => 0.1 do "You won!" end get '/win_a_car' do "Sorry, you lost." end ``` For a condition that takes multiple values use a splat: ``` ruby 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 on to the HTTP client, or at least the next middleware in the Rack stack. Most commonly, this is a string, as in the above examples. But other values are also accepted. You can return any object that would either be a valid Rack response, Rack body object or HTTP status code: * An Array with three elements: `[status (Fixnum), headers (Hash), response body (responds to #each)]` * An Array with two elements: `[status (Fixnum), response body (responds to #each)]` * An object that responds to `#each` and passes nothing but strings to the given block * A Fixnum representing the status code That way we can, for instance, easily implement a streaming example: ``` ruby class Stream def each 100.times { |i| yield "#{i}\n" } end end 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 and regular expressions as route matches. However, it does not stop there. You can easily define your own matchers: ``` ruby class AllButPattern Match = Struct.new(:captures) def initialize(except) @except = except @captures = Match.new([]) end def match(str) @captures unless @except === str end end def all_but(pattern) AllButPattern.new(pattern) end get all_but("/index") do # ... end ``` Note that the above example might be over-engineered, as it can also be expressed as: ``` ruby get // do pass if request.path_info == "/index" # ... end ``` Or, using negative look ahead: ``` ruby get %r{^(?!/index$)} do # ... end ``` ## Static Files Static files are served from the `./public` directory. You can specify a different location by setting the `:public_folder` option: ``` ruby set :public_folder, File.dirname(__FILE__) + '/static' ``` Note that the public directory name is not included in the URL. A file `./public/css/style.css` is made available as `http://example.com/css/style.css`. Use the `:static_cache_control` setting (see below) to add `Cache-Control` header info. ## Views / Templates Each template language is exposed via its own rendering method. These methods simply return a string: ``` ruby get '/' do erb :index end ``` This renders `views/index.erb`. Instead of a template name, you can also just pass in the template content directly: ``` ruby get '/' do code = "<%= Time.now %>" erb code end ``` Templates take a second argument, the options hash: ``` ruby get '/' do erb :index, :layout => :post end ``` This will render `views/index.erb` embedded in the `views/post.erb` (default is `views/layout.erb`, if it exists). Any options not understood by Sinatra will be passed on to the template engine: ``` ruby get '/' do haml :index, :format => :html5 end ``` You can also set options per template language in general: ``` ruby set :haml, :format => :html5 get '/' do haml :index end ``` Options passed to the render method override options set via `set`. Available Options:
locals
List of locals passed to the document. Handy with partials. Example: erb "<%= foo %>", :locals => {:foo => "bar"}
default_encoding
String encoding to use if uncertain. Defaults to settings.default_encoding.
views
Views folder to load templates from. Defaults to settings.views.
layout
Whether to use a layout (true or false), if it's a Symbol, specifies what template to use. Example: erb :index, :layout => !request.xhr?
content_type
Content-Type the template produces, default depends on template language.
scope
Scope to render template under. Defaults to the application instance. If you change this, instance variables and helper methods will not be available.
layout_engine
Template engine to use for rendering the layout. Useful for languages that do not support layouts otherwise. Defaults to the engine used for the template. Example: set :rdoc, :layout_engine => :erb
layout_options
Special options only used for rendering the layout. Example: set :rdoc, :layout_options => { :views => 'views/layouts' }
Templates are assumed to be located directly under the `./views` directory. To use a different views directory: `set :views, settings.root + '/templates'` One important thing to remember is that you always have to reference templates with symbols, even if they're in a subdirectory (in this case, use: `:'subdir/template'` or `'subdir/template'.to_sym`). You must use a symbol because otherwise rendering methods will render any strings passed to them directly. ### Literal Templates ``` ruby get '/' do haml '%div.title Hello World' end ``` Renders the template string. ### Available Template Languages Some languages have multiple implementations. To specify what implementation to use (and to be thread-safe), you should simply require it first: ``` ruby require 'rdiscount' # or require 'bluecloth' get('/') { markdown :index } ``` #### Haml Templates
Dependency haml
File Extension .haml
Example haml :index, :format => :html5
#### Erb Templates
Dependency erubis or erb (included in Ruby)
File Extensions .erb, .rhtml or .erubis (Erubis only)
Example erb :index
#### Builder Templates
Dependency builder
File Extension .builder
Example builder { |xml| xml.em "hi" }
It also takes a block for inline templates (see example). #### Nokogiri Templates
Dependency nokogiri
File Extension .nokogiri
Example nokogiri { |xml| xml.em "hi" }
It also takes a block for inline templates (see example). #### Sass Templates
Dependency sass
File Extension .sass
Example sass :stylesheet, :style => :expanded
#### SCSS Templates
Dependency sass
File Extension .scss
Example scss :stylesheet, :style => :expanded
#### Less Templates
Dependency less
File Extension .less
Example less :stylesheet
#### Liquid Templates
Dependency liquid
File Extension .liquid
Example liquid :index, :locals => { :key => 'value' }
Since you cannot call Ruby methods (except for `yield`) from a Liquid template, you almost always want to pass locals to it. #### Markdown Templates
Dependency Anyone of: RDiscount, RedCarpet, BlueCloth, kramdown, maruku
File Extensions .markdown, .mkd and .md
Example markdown :index, :layout_engine => :erb
It is not possible to call methods from markdown, nor to pass locals to it. You therefore will usually use it in combination with another rendering engine: ``` ruby erb :overview, :locals => { :text => markdown(:introduction) } ``` Note that you may also call the `markdown` method from within other templates: ``` ruby %h1 Hello From Haml! %p= markdown(:greetings) ``` Since you cannot call Ruby from Markdown, you cannot use layouts written in Markdown. However, it is possible to use another rendering engine for the template than for the layout by passing the `:layout_engine` option. #### Textile Templates
Dependency RedCloth
File Extension .textile
Example textile :index, :layout_engine => :erb
It is not possible to call methods from textile, nor to pass locals to it. You therefore will usually use it in combination with another rendering engine: ``` ruby erb :overview, :locals => { :text => textile(:introduction) } ``` Note that you may also call the `textile` method from within other templates: ``` ruby %h1 Hello From Haml! %p= textile(:greetings) ``` Since you cannot call Ruby from Textile, you cannot use layouts written in Textile. However, it is possible to use another rendering engine for the template than for the layout by passing the `:layout_engine` option. #### RDoc Templates
Dependency RDoc
File Extension .rdoc
Example rdoc :README, :layout_engine => :erb
It is not possible to call methods from rdoc, nor to pass locals to it. You therefore will usually use it in combination with another rendering engine: ``` ruby erb :overview, :locals => { :text => rdoc(:introduction) } ``` Note that you may also call the `rdoc` method from within other templates: ``` ruby %h1 Hello From Haml! %p= rdoc(:greetings) ``` Since you cannot call Ruby from RDoc, you cannot use layouts written in RDoc. However, it is possible to use another rendering engine for the template than for the layout by passing the `:layout_engine` option. #### Radius Templates
Dependency Radius
File Extension .radius
Example radius :index, :locals => { :key => 'value' }
Since you cannot call Ruby methods directly from a Radius template, you almost always want to pass locals to it. #### Markaby Templates
Dependency Markaby
File Extension .mab
Example markaby { h1 "Welcome!" }
It also takes a block for inline templates (see example). #### RABL Templates
Dependency Rabl
File Extension .rabl
Example rabl :index
#### Slim Templates
Dependency Slim Lang
File Extension .slim
Example slim :index
#### Creole Templates
Dependency Creole
File Extension .creole
Example creole :wiki, :layout_engine => :erb
It is not possible to call methods from creole, nor to pass locals to it. You therefore will usually use it in combination with another rendering engine: ``` ruby erb :overview, :locals => { :text => creole(:introduction) } ``` Note that you may also call the `creole` method from within other templates: ``` ruby %h1 Hello From Haml! %p= creole(:greetings) ``` Since you cannot call Ruby from Creole, you cannot use layouts written in Creole. However, it is possible to use another rendering engine for the template than for the layout by passing the `:layout_engine` option. #### CoffeeScript Templates
Dependency CoffeeScript and a way to execute javascript
File Extension .coffee
Example coffee :index
#### Stylus Templates
Dependency Stylus and a way to execute javascript
File Extension .styl
Example stylus :index
Before being able to use Stylus templates, you need to load `stylus` and `stylus/tilt` first: ``` ruby require 'sinatra' require 'stylus' require 'stylus/tilt' get '/' do stylus :example end ``` #### Yajl Templates
Dependency yajl-ruby
File Extension .yajl
Example yajl :index, :locals => { :key => 'qux' }, :callback => 'present', :variable => 'resource'
The template source is evaluated as a Ruby string, and the resulting json variable is converted using `#to_json`: ``` ruby json = { :foo => 'bar' } json[:baz] = key ``` The `:callback` and `:variable` options can be used to decorate the rendered object: ``` ruby var resource = {"foo":"bar","baz":"qux"}; present(resource); ``` #### WLang Templates
Dependency wlang
File Extension .wlang
Example wlang :index, :locals => { :key => 'value' }
Since calling ruby methods is not idiomatic in wlang, you almost always want to pass locals to it. Layouts written in wlang and `yield` are supported, though. ### Accessing Variables in Templates Templates are evaluated within the same context as route handlers. Instance variables set in route handlers are directly accessible by templates: ``` ruby get '/:id' do @foo = Foo.find(params[:id]) haml '%h1= @foo.name' end ``` Or, specify an explicit Hash of local variables: ``` ruby get '/:id' do foo = Foo.find(params[:id]) haml '%h1= bar.name', :locals => { :bar => foo } end ``` This is typically used when rendering templates as partials from within other templates. ### Templates with `yield` and nested layouts A layout is usually just a template that calls `yield`. Such a template can be used either through the `:template` option as described above, or it can be rendered with a block as follows: ``` ruby erb :post, :layout => false do erb :index end ``` This code is mostly equivalent to `erb :index, :layout => :post`. Passing blocks to rendering methods is most useful for creating nested layouts: ``` ruby erb :main_layout, :layout => false do erb :admin_layout do erb :user end end ``` This can also be done in fewer lines of code with: ``` ruby erb :admin_layout, :layout => :main_layout do erb :user end ``` Currently the following rendering method accept a block: `erb`, `haml`, `liquid`, `slim `, `wlang`. Also the general `render` method accepts a block. ### Inline Templates Templates may be defined at the end of the source file: ``` ruby require 'sinatra' get '/' do haml :index end __END__ @@ layout %html = yield @@ index %div.title Hello world. ``` NOTE: Inline templates defined in the source file that requires sinatra are automatically loaded. Call `enable :inline_templates` explicitly if you have inline templates in other source files. ### Named Templates Templates may also be defined using the top-level `template` method: ``` ruby template :layout do "%html\n =yield\n" end template :index do '%div.title Hello World!' end get '/' do haml :index end ``` If a template named "layout" exists, it will be used each time a template is rendered. You can individually disable layouts by passing `:layout => false` or disable them by default via `set :haml, :layout => false`: ``` ruby get '/' do haml :index, :layout => !request.xhr? end ``` ### Associating File Extensions To associate a file extension with a template engine, use `Tilt.register`. For instance, if you like to use the file extension `tt` for Textile templates, you can do the following: ``` ruby Tilt.register :tt, Tilt[:textile] ``` ### Adding Your Own Template Engine First, register your engine with Tilt, then create a rendering method: ``` ruby Tilt.register :myat, MyAwesomeTemplateEngine helpers do def myat(*args) render(:myat, *args) end end get '/' do myat :index end ``` Renders `./views/index.myat`. See https://github.com/rtomayko/tilt to learn more about Tilt. ## Filters Before filters are evaluated before each request within the same context as the routes will be and can modify the request and response. Instance variables set in filters are accessible by routes and templates: ``` ruby before do @note = 'Hi!' request.path_info = '/foo/bar/baz' end get '/foo/*' do @note #=> 'Hi!' params[:splat] #=> 'bar/baz' end ``` After filters are evaluated after each request within the same context and can also modify the request and response. Instance variables set in before filters and routes are accessible by after filters: ``` ruby after do puts response.status end ``` Note: Unless you use the `body` method rather than just returning a String from the routes, the body will not yet be available in the after filter, since it is generated later on. Filters optionally take a pattern, causing them to be evaluated only if the request path matches that pattern: ``` ruby before '/protected/*' do authenticate! end after '/create/:slug' do |slug| session[:last_slug] = slug end ``` Like routes, filters also take conditions: ``` ruby before :agent => /Songbird/ do # ... end after '/blog/*', :host_name => 'example.com' do # ... end ``` ## Helpers Use the top-level `helpers` method to define helper methods for use in route handlers and templates: ``` ruby helpers do def bar(name) "#{name}bar" end end get '/:name' do bar(params[:name]) end ``` Alternatively, helper methods can be separately defined in a module: ``` ruby module FooUtils def foo(name) "#{name}foo" end end module BarUtils def bar(name) "#{name}bar" end end helpers FooUtils, BarUtils ``` The effect is the same as including the modules in the application class. ### Using Sessions A session is used to keep state during requests. If activated, you have one session hash per user session: ``` ruby enable :sessions get '/' do "value = " << session[:value].inspect end get '/:value' do session[:value] = params[:value] end ``` Note that `enable :sessions` actually stores all data in a cookie. This might not always be what you want (storing lots of data will increase your traffic, for instance). You can use any Rack session middleware: in order to do so, do **not** call `enable :sessions`, but instead pull in your middleware of choice as you would any other middleware: ``` ruby use Rack::Session::Pool, :expire_after => 2592000 get '/' do "value = " << session[:value].inspect end get '/:value' do session[:value] = params[:value] end ``` To improve security, the session data in the cookie is signed with a session secret. A random secret is generated for you by Sinatra. However, since this secret will change with every start of your application, you might want to set the secret yourself, so all your application instances share it: ``` ruby set :session_secret, 'super secret' ``` If you want to configure it further, you may also store a hash with options in the `sessions` setting: ``` ruby set :sessions, :domain => 'foo.com' ``` ### Halting To immediately stop a request within a filter or route use: ``` ruby halt ``` You can also specify the status when halting: ``` ruby halt 410 ``` Or the body: ``` ruby halt 'this will be the body' ``` Or both: ``` ruby halt 401, 'go away!' ``` With headers: ``` ruby halt 402, {'Content-Type' => 'text/plain'}, 'revenge' ``` It is of course possible to combine a template with `halt`: ``` ruby halt erb(:error) ``` ### Passing A route can punt processing to the next matching route using `pass`: ``` ruby get '/guess/:who' do pass unless params[:who] == 'Frank' 'You got me!' end get '/guess/*' do 'You missed!' end ``` The route block is immediately exited and control continues with the next matching route. If no matching route is found, a 404 is returned. ### Triggering Another Route Sometimes `pass` is not what you want, instead you would like to get the result of calling another route. Simply use `call` to achieve this: ``` ruby get '/foo' do status, headers, body = call env.merge("PATH_INFO" => '/bar') [status, headers, body.map(&:upcase)] end get '/bar' do "bar" end ``` Note that in the example above, you would ease testing and increase performance by simply moving `"bar"` into a helper used by both `/foo` and `/bar`. If you want the request to be sent to the same application instance rather than a duplicate, use `call!` instead of `call`. Check out the Rack specification if you want to learn more about `call`. ### Setting Body, Status Code and Headers It is possible and recommended to set the status code and response body with the return value of the route block. However, in some scenarios you might want to set the body at an arbitrary point in the execution flow. You can do so with the `body` helper method. If you do so, you can use that method from there on to access the body: ``` ruby get '/foo' do body "bar" end after do puts body end ``` It is also possible to pass a block to `body`, which will be executed by the Rack handler (this can be used to implement streaming, see "Return Values"). Similar to the body, you can also set the status code and headers: ``` ruby get '/foo' do status 418 headers \ "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: ``` ruby 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 the 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 requests, 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` finishes executing. Streaming does not work at all with Shotgun. 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: ``` ruby # long polling set :server, :thin connections = [] get '/subscribe' do # register a client's interest in server events stream(:keep_open) { |out| connections << out } # purge dead connections connections.reject!(&:closed?) # acknowledge "subscribed" end post '/message' do connections.each do |out| # notify client that a new message has arrived out << params[:message] << "\n" # indicate client to connect again out.close end # acknowledge "message received" end ``` ### Logging In the request scope, the `logger` helper exposes a `Logger` instance: ``` ruby get '/' do logger.info "loading data" # ... end ``` This logger will automatically take your Rack handler's logging settings into account. If logging is disabled, this method will return a dummy object, so you do not have to worry in your routes and filters about it. Note that logging is only enabled for `Sinatra::Application` by default, so if you inherit from `Sinatra::Base`, you probably want to enable it yourself: ``` ruby class MyApp < Sinatra::Base configure :production, :development do enable :logging end end ``` To avoid any logging middleware to be set up, set the `logging` setting to `nil`. However, keep in mind that `logger` will in that case return `nil`. A common use case is when you want to set your own logger. Sinatra will use whatever it will find in `env['rack.logger']`. ### Mime Types When using `send_file` or static files you may have mime types Sinatra doesn't understand. Use `mime_type` to register them by file extension: ``` ruby configure do mime_type :foo, 'text/foo' end ``` You can also use it with the `content_type` helper: ``` ruby get '/' do content_type :foo "foo foo foo" end ``` ### Generating URLs For generating URLs you should use the `url` helper method, for instance, in Haml: ``` haml %a{:href => url('/foo')} foo ``` It takes reverse proxies and Rack routers into account, if present. This method is also aliased to `to` (see below for an example). ### Browser Redirect You can trigger a browser redirect with the `redirect` helper method: ``` ruby get '/foo' do redirect to('/bar') end ``` Any additional parameters are handled like arguments passed to `halt`: ``` ruby redirect to('/bar'), 303 redirect 'http://google.com', 'wrong place, buddy' ``` You can also easily redirect back to the page the user came from with `redirect back`: ``` ruby get '/foo' do "do something" end get '/bar' do do_something redirect back end ``` To pass arguments with a redirect, either add them to the query: ``` ruby redirect to('/bar?sum=42') ``` Or use a session: ``` ruby enable :sessions get '/foo' do session[:secret] = 'foo' redirect to('/bar') end get '/bar' do session[:secret] end ``` ### Cache Control Setting your headers correctly is the foundation for proper HTTP caching. You can easily set the Cache-Control header like this: ``` ruby get '/' do cache_control :public "cache it!" end ``` Pro tip: Set up caching in a before filter: ``` ruby before do cache_control :public, :must_revalidate, :max_age => 60 end ``` If you are using the `expires` helper to set the corresponding header, `Cache-Control` will be set automatically for you: ``` ruby before do expires 500, :public, :must_revalidate end ``` To properly use caches, you should consider using `etag` or `last_modified`. It is recommended to call those helpers *before* doing any heavy lifting, as they will immediately flush a response if the client already has the current version in its cache: ``` ruby get '/article/:id' do @article = Article.find params[:id] last_modified @article.updated_at etag @article.sha1 erb :article end ``` It is also possible to use a [weak ETag](http://en.wikipedia.org/wiki/HTTP_ETag#Strong_and_weak_validation): ``` ruby 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 reverse-proxy caching solution, try [rack-cache](https://github.com/rtomayko/rack-cache): ``` ruby require "rack/cache" require "sinatra" use Rack::Cache get '/' do cache_control :public, :max_age => 36000 sleep 5 "hello" end ``` Use the `:static_cache_control` setting (see below) to add `Cache-Control` header info to static files. According to RFC 2616 your application should behave differently if the If-Match or If-None-Match header is set to `*` depending on whether the resource requested is already in existence. Sinatra assumes resources for safe (like get) and idempotent (like put) requests are already in existence, whereas other resources (for instance for post requests), are treated as new resources. You can change this behavior by passing in a `:new_resource` option: ``` ruby get '/create' do etag '', :new_resource => true Article.create erb :new_article end ``` If you still want to use a weak ETag, pass in a `:kind` option: ``` ruby etag '', :new_resource => true, :kind => :weak ``` ### Sending Files For sending files, you can use the `send_file` helper method: ``` ruby get '/' do send_file 'foo.png' end ``` It also takes options: ``` ruby send_file 'foo.png', :type => :jpg ``` The options are:
filename
file name, in response, defaults to the real file name.
last_modified
value for Last-Modified header, defaults to the file's mtime.
type
content type to use, guessed from the file extension if missing.
disposition
used for Content-Disposition, possible value: nil (default), :attachment and :inline
length
Content-Length header, defaults to file size.
status
Status code to be send. Useful when sending a static file as an error page. If supported by the Rack handler, other means than streaming from the Ruby process will be used. If you use this helper method, Sinatra will automatically handle range requests.
### Accessing the Request Object The incoming request object can be accessed from request level (filter, routes, error handlers) through the `request` method: ``` ruby # app running on 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 sent by the client (see below) request.scheme # "http" request.script_name # "/example" request.path_info # "/foo" request.port # 80 request.request_method # "GET" request.query_string # "" request.content_length # length of request.body request.media_type # media type of request.body request.host # "example.com" request.get? # true (similar methods for other verbs) request.form_data? # false request["some_param"] # value of some_param parameter. [] is a shortcut to the params hash. request.referrer # the referrer of the client or '/' request.user_agent # user agent (used by :agent condition) request.cookies # hash of browser cookies request.xhr? # is this an ajax request? request.url # "http://example.com/example/foo" request.path # "/example/foo" request.ip # client IP address request.secure? # false (would be true over ssl) request.forwarded? # true (if running behind a reverse proxy) request.env # raw env hash handed in by Rack end ``` Some options, like `script_name` or `path_info`, can also be written: ``` ruby before { request.path_info = "/" } get "/" do "all requests end up here" end ``` The `request.body` is an IO or StringIO object: ``` ruby post "/api" do request.body.rewind # in case someone already read it data = JSON.parse request.body.read "Hello #{data['name']}!" end ``` ### Attachments You can use the `attachment` helper to tell the browser the response should be stored on disk rather than displayed in the browser: ``` ruby get '/' do attachment "store it!" end ``` You can also pass it a file name: ``` ruby get '/' do attachment "info.txt" "store it!" end ``` ### Dealing with Date and Time Sinatra offers a `time_for` helper method that generates a Time object from the given value. It is also able to convert `DateTime`, `Date` and similar classes: ``` ruby 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: ``` ruby helpers do def time_for(value) case value when :yesterday then Time.now - 24*60*60 when :tomorrow then Time.now + 24*60*60 else super end end end get '/' do last_modified :yesterday expires :tomorrow "hello" end ``` ### Looking Up Template Files The `find_template` helper is used to find template files for rendering: ``` ruby find_template settings.views, 'foo', Tilt[:haml] do |file| puts "could be #{file}" end ``` This is not really useful. But it is useful that you can actually override this method to hook in your own lookup mechanism. For instance, if you want to be able to use more than one view directory: ``` ruby set :views, ['views', 'templates'] helpers do def find_template(views, name, engine, &block) Array(views).each { |v| super(v, name, engine, &block) } end end ``` Another example would be using different directories for different engines: ``` ruby set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views' helpers do def find_template(views, name, engine, &block) _, folder = views.detect { |k,v| engine == Tilt[k] } folder ||= views[:default] super(folder, name, engine, &block) end end ``` You can also easily wrap this up in an extension and share with others! Note that `find_template` does not check if the file really exists but rather calls the given block for all possible paths. This is not a performance issue, since `render` will use `break` as soon as a file is found. Also, template locations (and content) will be cached if you are not running in development mode. You should keep that in mind if you write a really crazy method. ## Configuration Run once, at startup, in any environment: ``` ruby configure do # setting one option set :option, 'value' # setting multiple options set :a => 1, :b => 2 # same as `set :option, true` enable :option # same as `set :option, false` disable :option # you can also have dynamic settings with blocks set(:css_dir) { File.join(views, 'css') } end ``` Run only when the environment (`RACK_ENV` environment variable) is set to `:production`: ``` ruby configure :production do ... end ``` Run when the environment is set to either `:production` or `:test`: ```ruby configure :production, :test do ... end ``` You can access those options via `settings`: ``` ruby configure do set :foo, 'bar' end get '/' do settings.foo? # => true settings.foo # => 'bar' ... end ``` ### Configuring attack protection Sinatra is using [Rack::Protection](https://github.com/rkh/rack-protection#readme) to defend your application against common, opportunistic attacks. You can easily disable this behavior (which will open up your application to tons of common vulnerabilities): ``` ruby disable :protection ``` To skip a single defense layer, set `protection` to an options hash: ``` ruby set :protection, :except => :path_traversal ``` You can also hand in an array in order to disable a list of protections: ``` ruby set :protection, :except => [:path_traversal, :session_hijacking] ``` By default, Sinatra will only set up session based protection if `:sessions` has been enabled. Sometimes you want to set up sessions on your own, though. In that case you can get it to set up session based protections by passing the `:session` option: ``` ruby use Rack::Session::Pool set :protection, :session => true ``` ### Available Settings
absolute_redirects
If disabled, Sinatra will allow relative redirects, however, Sinatra will no longer conform with RFC 2616 (HTTP 1.1), which only allows absolute redirects.
Enable if your app is running behind a reverse proxy that has not been set up properly. Note that the url helper will still produce absolute URLs, unless you pass in false as the second parameter.
Disabled by default.
add_charsets
mime types the content_type helper will automatically add the charset info to. You should add to it rather than overriding this option: settings.add_charsets << "application/foobar"
app_file
Path to the main application file, used to detect project root, views and public folder and inline templates.
bind
IP address to bind to (default: 0.0.0.0 or localhost if your `environment` is set to development.). Only used for built-in server.
default_encoding
encoding to assume if unknown (defaults to "utf-8").
dump_errors
display errors in the log.
environment
current environment, defaults to ENV['RACK_ENV'], or "development" if not available.
logging
use the logger.
lock
Places a lock around every request, only running processing on request per Ruby process concurrently.
Enabled if your app is not thread-safe. Disabled per default.
method_override
use _method magic to allow put/delete forms in browsers that don't support it.
port
Port to listen on. Only used for built-in server.
prefixed_redirects
Whether or not to insert request.script_name into redirects if no absolute path is given. That way redirect '/foo' would behave like redirect to('/foo'). Disabled per default.
protection
Whether or not to enable web attack protections. See protection section above.
public_dir
Alias for public_folder. See below.
public_folder
Path to the folder public files are served from. Only used if static file serving is enabled (see static setting below). Inferred from app_file setting if not set.
reload_templates
Whether or not to reload templates between requests. Enabled in development mode.
root
Path to project root folder. Inferred from app_file setting if not set.
raise_errors
raise exceptions (will stop application). Enabled by default when environment is set to "test", disabled otherwise.
run
if enabled, Sinatra will handle starting the web server, do not enable if using rackup or other means.
running
is the built-in server running now? Do not change this setting!
server
Server or list of servers to use for built-in server. Order indicates priority, default depends on Ruby implementation.
sessions
Enable cookie-based sessions support using Rack::Session::Cookie. See 'Using Sessions' section for more information.
show_exceptions
Show a stack trace in the browser when an exception happens. Enabled by default when environment is set to "development", disabled otherwise.
Can also be set to :after_handler to trigger app-specified error handling before showing a stack trace in the browser.
static
Whether Sinatra should handle serving static files.
Disable when using a server able to do this on its own.
Disabling will boost performance.
Enabled per default in classic style, disabled for modular apps.
static_cache_control
When Sinatra is serving static files, set this to add Cache-Control headers to the responses. Uses the cache_control helper. Disabled by default.
Use an explicit array when setting multiple values: set :static_cache_control, [:public, :max_age => 300]
threaded
If set to true, will tell Thin to use EventMachine.defer for processing the request.
views
Path to the views folder. Inferred from app_file setting if not set.
x_cascade
Whether or not to set the X-Cascade header if no route matches. Defaults to true.
## Environments There are three predefined `environments`: `"development"`, `"production"` and `"test"`. Environments can be set through the `RACK_ENV` environment variable. The default value is `"development"`. In the `"development"` environment all templates are reloaded between requests, and special `not_found` and `error` handlers display stack traces in your browser. In the `"production"` and `"test"` environments, templates are cached by default. To run different environments, set the `RACK_ENV` environment variable: ``` shell RACK_ENV=production ruby my_app.rb ``` You can use predefined methods: `development?`, `test?` and `production?` to check the current environment setting: ``` ruby get '/' do if settings.development? "development!" else "not development!" end end ``` ## Error Handling Error handlers run within the same context as routes and before filters, which means you get all the goodies it has to offer, like `haml`, `erb`, `halt`, etc. ### Not Found When a `Sinatra::NotFound` exception is raised, or the response's status code is 404, the `not_found` handler is invoked: ``` ruby not_found do 'This is nowhere to be found.' end ``` ### Error The `error` handler is invoked any time an exception is raised from a route block or a filter. The exception object can be obtained from the `sinatra.error` Rack variable: ``` ruby error do 'Sorry there was a nasty error - ' + env['sinatra.error'].name end ``` Custom errors: ``` ruby error MyCustomError do 'So what happened was...' + env['sinatra.error'].message end ``` Then, if this happens: ``` ruby get '/' do raise MyCustomError, 'something bad' end ``` You get this: ``` So what happened was... something bad ``` Alternatively, you can install an error handler for a status code: ``` ruby error 403 do 'Access forbidden' end get '/secret' do 403 end ``` Or a range: ``` ruby error 400..510 do 'Boom' end ``` Sinatra installs special `not_found` and `error` handlers when running under the development environment to display nice stack traces and additional debugging information in your browser. ## Rack Middleware Sinatra rides on [Rack](http://rack.rubyforge.org/), a minimal standard interface for Ruby web frameworks. One of Rack's most interesting capabilities for application developers is support for "middleware" -- components that sit between the server and your application monitoring and/or manipulating the HTTP request/response to provide various types of common functionality. Sinatra makes building Rack middleware pipelines a cinch via a top-level `use` method: ``` ruby require 'sinatra' require 'my_custom_middleware' use Rack::Lint use MyCustomMiddleware get '/hello' do 'Hello World' end ``` The semantics of `use` are identical to those defined for the [Rack::Builder](http://rack.rubyforge.org/doc/classes/Rack/Builder.html) DSL (most frequently used from rackup files). For example, the `use` method accepts multiple/variable args as well as blocks: ``` ruby use Rack::Auth::Basic do |username, password| username == 'admin' && password == 'secret' end ``` Rack is distributed with a variety of standard middleware for logging, debugging, URL routing, authentication, and session handling. Sinatra uses many of these components automatically based on configuration so you typically don't have to `use` them explicitly. You can find useful middleware in [rack](https://github.com/rack/rack/tree/master/lib/rack), [rack-contrib](https://github.com/rack/rack-contrib#readm), with [CodeRack](http://coderack.org/) or in the [Rack wiki](https://github.com/rack/rack/wiki/List-of-Middleware). ## Testing Sinatra tests can be written using any Rack-based testing library or framework. [Rack::Test](http://rdoc.info/github/brynary/rack-test/master/frames) is recommended: ``` ruby require 'my_sinatra_app' require 'test/unit' require 'rack/test' class MyAppTest < Test::Unit::TestCase include Rack::Test::Methods def app Sinatra::Application end def test_my_default get '/' assert_equal 'Hello World!', last_response.body end def test_with_params get '/meet', :name => 'Frank' assert_equal 'Hello Frank!', last_response.body end def test_with_rack_env get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' assert_equal "You're using Songbird!", last_response.body end end ``` Note: If you are using Sinatra in the modular style, replace `Sinatra::Application` above with the class name of your app. ## Sinatra::Base - Middleware, Libraries, and Modular Apps Defining your app at the top-level works well for micro-apps but has considerable drawbacks when building reusable components such as Rack middleware, Rails metal, simple libraries with a server component, or even Sinatra extensions. The top-level assumes a micro-app style configuration (e.g., a single application file, `./public` and `./views` directories, logging, exception detail page, etc.). That's where `Sinatra::Base` comes into play: ``` ruby require 'sinatra/base' class MyApp < Sinatra::Base set :sessions, true set :foo, 'bar' get '/' do 'Hello world!' end end ``` The methods available to `Sinatra::Base` subclasses are exactly the same as those available via the top-level DSL. Most top-level apps can be converted to `Sinatra::Base` components with two modifications: * Your file should require `sinatra/base` instead of `sinatra`; otherwise, all of Sinatra's DSL methods are imported into the main namespace. * Put your app's routes, error handlers, filters, and options in a subclass of `Sinatra::Base`. `Sinatra::Base` is a blank slate. Most options are disabled by default, including the built-in server. See [Options and Configuration](http://sinatra.github.com/configuration.html) for details on available options and their behavior. ### Modular vs. Classic Style Contrary to common belief, there is nothing wrong with the classic style. If it suits your application, you do not have to switch to a modular application. The main disadvantage of using the classic style rather than the modular style is that you will only have one Sinatra application per Ruby process. If you plan to use more than one, switch to the modular style. There is no reason you cannot mix the modular and the classic styles. If switching from one style to the other, you should be aware of slightly different default settings:
Setting Classic Modular
app_file file loading sinatra file subclassing Sinatra::Base
run $0 == app_file false
logging true false
method_override true false
inline_templates true false
static true false
### Serving a Modular Application There are two common options for starting a modular app, actively starting with `run!`: ``` ruby # my_app.rb require 'sinatra/base' class MyApp < Sinatra::Base # ... app code here ... # start the server if ruby file executed directly run! if app_file == $0 end ``` Start with: ``` ruby ruby my_app.rb ``` Or with a `config.ru` file, which allows using any Rack handler: ``` ruby # config.ru (run with rackup) require './my_app' run MyApp ``` Run: ``` ruby rackup -p 4567 ``` ### Using a Classic Style Application with a config.ru Write your app file: ``` ruby # app.rb require 'sinatra' get '/' do 'Hello world!' end ``` And a corresponding `config.ru`: ``` ruby require './app' run Sinatra::Application ``` ### When to use a config.ru? A `config.ru` file is recommended if: * You want to deploy with a different Rack handler (Passenger, Unicorn, Heroku, ...). * You want to use more than one subclass of `Sinatra::Base`. * You want to use Sinatra only for middleware, and not as an endpoint. **There is no need to switch to a `config.ru` simply because you switched to the modular style, and you don't have to use the modular style for running with a `config.ru`.** ### Using Sinatra as Middleware Not only is Sinatra able to use other Rack middleware, any Sinatra application can in turn be added in front of any Rack endpoint as middleware itself. This endpoint could be another Sinatra application, or any other Rack-based application (Rails/Ramaze/Camping/...): ``` ruby require 'sinatra/base' class LoginScreen < Sinatra::Base enable :sessions get('/login') { haml :login } post('/login') do if params[:name] == 'admin' && params[:password] == 'admin' session['user_name'] = params[:name] else redirect '/login' end end end class MyApp < Sinatra::Base # middleware will run before filters use LoginScreen before do unless session['user_name'] halt "Access denied, please login." end end get('/') { "Hello #{session['user_name']}." } end ``` ### Dynamic Application Creation Sometimes you want to create new applications at runtime without having to assign them to a constant, you can do this with `Sinatra.new`: ``` ruby require 'sinatra/base' my_app = Sinatra.new { get('/') { "hi" } } my_app.run! ``` It takes the application to inherit from as an optional argument: ```ruby # config.ru (run with rackup) 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 ``` This is especially useful for testing Sinatra extensions or using Sinatra in your own library. This also makes using Sinatra as middleware extremely easy: ``` ruby require 'sinatra/base' use Sinatra do get('/') { ... } end run RailsProject::Application ``` ## Scopes and Binding The scope you are currently in determines what methods and variables are available. ### Application/Class Scope Every Sinatra application corresponds to a subclass of `Sinatra::Base`. If you are using the top-level DSL (`require 'sinatra'`), then this class is `Sinatra::Application`, otherwise it is the subclass you created explicitly. At class level you have methods like `get` or `before`, but you cannot access the `request` or `session` objects, as there is only a single application class for all requests. Options created via `set` are methods at class level: ``` ruby class MyApp < Sinatra::Base # Hey, I'm in the application scope! set :foo, 42 foo # => 42 get '/foo' do # Hey, I'm no longer in the application scope! end end ``` You have the application scope binding inside: * Your application class body * Methods defined by extensions * The block passed to `helpers` * Procs/blocks used as value for `set` * The block passed to `Sinatra.new` You can reach the scope object (the class) like this: * Via the object passed to configure blocks (`configure { |c| ... }`) * `settings` from within the request scope ### Request/Instance Scope For every incoming request, a new instance of your application class is created and all handler blocks run in that scope. From within this scope you can access the `request` and `session` objects or call rendering methods like `erb` or `haml`. You can access the application scope from within the request scope via the `settings` helper: ``` ruby class MyApp < Sinatra::Base # Hey, I'm in the application scope! get '/define_route/:name' do # Request scope for '/define_route/:name' @value = 42 settings.get("/#{params[:name]}") do # Request scope for "/#{params[:name]}" @value # => nil (not the same request) end "Route defined!" end end ``` You have the request scope binding inside: * get, head, post, put, delete, options, patch, link, and unlink blocks * before and after filters * helper methods * templates/views ### Delegation Scope The delegation scope just forwards methods to the class scope. However, it does not behave exactly like the class scope, as you do not have the class binding. Only methods explicitly marked for delegation are available, and you do not share variables/state with the class scope (read: you have a different `self`). You can explicitly add method delegations by calling `Sinatra::Delegator.delegate :method_name`. You have the delegate scope binding inside: * The top level binding, if you did `require "sinatra"` * An object extended with the `Sinatra::Delegator` mixin Have a look at the code for yourself: here's the [Sinatra::Delegator mixin](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/base.rb#L1609-1633) being [extending the main object](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/main.rb#L28-30). ## Command Line Sinatra applications can be run directly: ``` ruby ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER] ``` Options are: ``` -h # help -p # set the port (default is 4567) -o # set the host (default is 0.0.0.0) -e # set the environment (default is development) -s # specify rack server/handler (default is thin) -x # turn on the mutex lock (default is off) ``` ## Requirement 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 or switching to JRuby or Rubinius. Support for 1.8.7 will not be dropped before Sinatra 2.0. Ruby 1.8.6 is no longer supported.
Ruby 1.9.2
1.9.2 is fully supported. Do not use 1.9.2p0, as it is known to cause segmentation faults when running Sinatra. Official support will continue at least until the release of Sinatra 1.5.
Ruby 1.9.3
1.9.3 is fully supported and recommended. Please note that switching to 1.9.3 from an earlier version will invalidate all sessions. 1.9.3 will be supported until the release of Sinatra 2.0.
Ruby 2.0.0
2.0.0 is fully supported and recommended. There are currently no plans to drop official support for it.
Rubinius
Rubinius is officially supported (Rubinius >= 2.x). It is recommendended to gem install puma.
JRuby
The latest stable release of JRuby is officially supported. It is not recommended to use C extensions with JRuby. It is recommended to gem install trinidad.
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 (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 2.1.0), but we can't guarantee anything, since it is constantly moving. Expect 2.1.0 to be fully supported. Sinatra should work on any operating system supported by the chosen Ruby implementation. If you run MacRuby, you should `gem install control_tower`. Sinatra currently doesn't run on Cardinal, SmallRuby, BlueRuby or any Ruby version prior to 1.8.7. ## The Bleeding Edge If you would like to use Sinatra's latest bleeding-edge code, feel free to run your application against the master branch, it should be rather stable. We also push out prerelease gems from time to time, so you can do a ``` shell gem install sinatra --pre ``` To get some of the latest features. ### With Bundler If you want to run your application with the latest Sinatra, using [Bundler](http://gembundler.com/) is the recommended way. First, install bundler, if you haven't: ``` shell gem install bundler ``` Then, in your project directory, create a `Gemfile`: ```ruby source 'https://rubygems.org' gem 'sinatra', :github => "sinatra/sinatra" # other dependencies gem 'haml' # for instance, if you use haml gem 'activerecord', '~> 3.0' # maybe you also need ActiveRecord 3.x ``` Note that you will have to list all your application's dependencies in the `Gemfile`. Sinatra's direct dependencies (Rack and Tilt) will, however, be automatically fetched and added by Bundler. Now you can run your app like this: ``` shell bundle exec ruby myapp.rb ``` ### Roll Your Own Create a local clone and run your app with the `sinatra/lib` directory on the `$LOAD_PATH`: ``` shell cd myapp git clone git://github.com/sinatra/sinatra.git ruby -I sinatra/lib myapp.rb ``` To update the Sinatra sources in the future: ``` shell cd myapp/sinatra git pull ``` ### Install Globally You can build the gem on your own: ``` shell git clone git://github.com/sinatra/sinatra.git cd sinatra rake sinatra.gemspec rake install ``` If you install gems as root, the last step should be ``` shell sudo rake install ``` ## Versioning Sinatra follows [Semantic Versioning](http://semver.org/), both SemVer and SemVerTag. ## Further Reading * [Project Website](http://www.sinatrarb.com/) - Additional documentation, news, and links to other resources. * [Contributing](http://www.sinatrarb.com/contributing) - Find a bug? Need help? Have a patch? * [Issue tracker](http://github.com/sinatra/sinatra/issues) * [Twitter](http://twitter.com/sinatra) * [Mailing List](http://groups.google.com/group/sinatrarb/topics) * IRC: [#sinatra](irc://chat.freenode.net/#sinatra) on http://freenode.net * [Sinatra Book](http://sinatra-book.gittr.com) Cookbook Tutorial * [Sinatra Recipes](http://recipes.sinatrarb.com/) Community contributed recipes * API documentation for the [latest release](http://rubydoc.info/gems/sinatra) or the [current HEAD](http://rubydoc.info/github/sinatra/sinatra) on http://rubydoc.info * [CI server](http://travis-ci.org/sinatra/sinatra) sinatra-1.4.3/Rakefile0000644000004100000410000001373312161612727014671 0ustar www-datawww-datarequire 'rake/clean' require 'rake/testtask' require 'fileutils' require 'date' # CI Reporter is only needed for the CI begin require 'ci/reporter/rake/test_unit' rescue LoadError end task :default => :test task :spec => :test CLEAN.include "**/*.rbc" def source_version @source_version ||= begin load './lib/sinatra/version.rb' Sinatra::VERSION end end def prev_feature source_version.gsub(/^(\d\.)(\d+)\..*$/) { $1 + ($2.to_i - 1).to_s } end def prev_version return prev_feature + '.0' if source_version.end_with? '.0' source_version.gsub(/\d+$/) { |s| s.to_i - 1 } end # SPECS =============================================================== task :test do ENV['LANG'] = 'C' ENV.delete 'LC_CTYPE' end Rake::TestTask.new(:test) do |t| t.test_files = FileList['test/*_test.rb'] t.ruby_opts = ['-rubygems'] if defined? Gem t.ruby_opts << '-I.' end Rake::TestTask.new(:"test:core") do |t| core_tests = %w[base delegator encoding extensions filter helpers mapped_error middleware radius rdoc readme request response result route_added_hook routing server settings sinatra static templates] t.test_files = core_tests.map {|n| "test/#{n}_test.rb"} t.ruby_opts = ["-rubygems"] if defined? Gem t.ruby_opts << "-I." end # Rcov ================================================================ namespace :test do desc 'Measures test coverage' task :coverage do rm_f "coverage" sh "rcov -Ilib test/*_test.rb" end end # Website ============================================================= desc 'Generate RDoc under doc/api' task 'doc' => ['doc:api'] task('doc:api') { sh "yardoc -o doc/api" } CLEAN.include 'doc/api' # README =============================================================== task :add_template, [:name] do |t, args| Dir.glob('README.*') do |file| code = File.read(file) if code =~ /^===.*#{args.name.capitalize}/ puts "Already covered in #{file}" else template = code[/===[^\n]*Liquid.*index\.liquid<\/tt>[^\n]*/m] if !template puts "Liquid not found in #{file}" else puts "Adding section to #{file}" template = template.gsub(/Liquid/, args.name.capitalize).gsub(/liquid/, args.name.downcase) code.gsub! /^(\s*===.*CoffeeScript)/, "\n" << template << "\n\\1" File.open(file, "w") { |f| f << code } end end end end # Thanks in announcement =============================================== team = ["Ryan Tomayko", "Blake Mizerany", "Simon Rozet", "Konstantin Haase"] desc "list of contributors" task :thanks, [:release,:backports] do |t, a| a.with_defaults :release => "#{prev_version}..HEAD", :backports => "#{prev_feature}.0..#{prev_feature}.x" included = `git log --format=format:"%aN\t%s" #{a.release}`.lines.map { |l| l.force_encoding('binary') } excluded = `git log --format=format:"%aN\t%s" #{a.backports}`.lines.map { |l| l.force_encoding('binary') } commits = (included - excluded).group_by { |c| c[/^[^\t]+/] } authors = commits.keys.sort_by { |n| - commits[n].size } - team puts authors[0..-2].join(', ') << " and " << authors.last, "(based on commits included in #{a.release}, but not in #{a.backports})" end desc "list of authors" task :authors, [:commit_range, :format, :sep] do |t, a| a.with_defaults :format => "%s (%d)", :sep => ", ", :commit_range => '--all' authors = Hash.new { |h,k| h[k] = 0 } blake = "Blake Mizerany" overall = 0 mapping = { "blake.mizerany@gmail.com" => blake, "bmizerany" => blake, "a_user@mac.com" => blake, "ichverstehe" => "Harry Vangberg", "Wu Jiang (nouse)" => "Wu Jiang" } `git shortlog -s #{a.commit_range}`.lines.map do |line| line = line.force_encoding 'binary' if line.respond_to? :force_encoding num, name = line.split("\t", 2).map(&:strip) authors[mapping[name] || name] += num.to_i overall += num.to_i end puts "#{overall} commits by #{authors.count} authors:" puts authors.sort_by { |n,c| -c }.map { |e| a.format % e }.join(a.sep) end desc "generates TOC" task :toc, [:readme] do |t, a| a.with_defaults :readme => 'README.md' def self.link(title) title.downcase.gsub(/(?!-)\W /, '-').gsub(' ', '-').gsub(/(?!-)\W/, '') end puts "* [Sinatra](#sinatra)" title = Regexp.new('(?<=\* )(.*)') # so Ruby 1.8 doesn't complain File.binread(a.readme).scan(/^##.*/) do |line| puts line.gsub(/#(?=#)/, ' ').gsub('#', '*').gsub(title) { "[#{$1}](##{link($1)})" } end end # PACKAGING ============================================================ if defined?(Gem) # Load the gemspec using the same limitations as github def spec require 'rubygems' unless defined? Gem::Specification @spec ||= eval(File.read('sinatra.gemspec')) end def package(ext='') "pkg/sinatra-#{spec.version}" + ext end desc 'Build packages' task :package => %w[.gem .tar.gz].map {|e| package(e)} desc 'Build and install as local gem' task :install => package('.gem') do sh "gem install #{package('.gem')}" end directory 'pkg/' CLOBBER.include('pkg') file package('.gem') => %w[pkg/ sinatra.gemspec] + spec.files do |f| sh "gem build sinatra.gemspec" mv File.basename(f.name), f.name end file package('.tar.gz') => %w[pkg/] + spec.files do |f| sh <<-SH git archive \ --prefix=sinatra-#{source_version}/ \ --format=tar \ HEAD | gzip > #{f.name} SH end task 'release' => ['test', package('.gem')] do if File.binread("CHANGES") =~ /= \d\.\d\.\d . not yet released$/i fail 'please update changes first' unless %x{git symbolic-ref HEAD} == "refs/heads/prerelease\n" end sh <<-SH gem install #{package('.gem')} --local && gem push #{package('.gem')} && git commit --allow-empty -a -m '#{source_version} release' && git tag -s v#{source_version} -m '#{source_version} release' && git tag -s #{source_version} -m '#{source_version} release' && git push && (git push sinatra || true) && git push --tags && (git push sinatra --tags || true) SH end end sinatra-1.4.3/README.jp.md0000644000004100000410000010334312161612727015110 0ustar www-datawww-data# Sinatra *注) 本文書は英語から翻訳したものであり、その内容が最新でない場合もあります。最新の情報はオリジナルの英語版を参照して下さい。* [DSL](http://ja.wikipedia.org/wiki/ドメイン固有言語)です。 # myapp.rb require 'sinatra' get '/' do 'Hello world!' end gemをインストールして動かしてみる。 gem install sinatra ruby myapp.rb [localhost:4567](http://localhost:4567) を見る。 ## ルート Sinatraでは、ルートはHTTPメソッドとURLマッチングパターンがペアになっています。 ルートはブロックに結び付けられています。 get '/' do .. 何か見せる .. end post '/' do .. 何か生成する .. end put '/' do .. 何か更新する .. end delete '/' do .. 何か削除する .. end ルートは定義された順番にマッチします。 リクエストに最初にマッチしたルートが呼び出されます。 ルートのパターンは名前付きパラメータを含むことができ、 `params`ハッシュで取得できます。 get '/hello/:name' do # matches "GET /hello/foo" and "GET /hello/bar" # params[:name] is 'foo' or 'bar' "Hello #{params[:name]}!" end また、ブロックパラメータで名前付きパラメータにアクセスすることもできます。 get '/hello/:name' do |n| "Hello #{n}!" end ルートパターンはsplat(またはワイルドカード)を含むこともでき、 `params[:splat]` で取得できます。 get '/say/*/to/*' do # matches /say/hello/to/world params[:splat] # => ["hello", "world"] end get '/download/*.*' do # matches /download/path/to/file.xml params[:splat] # => ["path/to/file", "xml"] end ブロックパラーメータを使用した場合: get '/download/*.*' do |path, ext| [path, ext] # => ["path/to/file", "xml"] end 正規表現を使ったルート: get %r{/hello/([\w]+)} do "Hello, #{params[:captures].first}!" end ブロックパラーメータを使用した場合: get %r{/hello/([\w]+)} do |c| "Hello, #{c}!" end ### 条件 ルートにはユーザエージェントのようなさまざまな条件を含めることができます。 get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do "You're using Songbird version #{params[:agent][0]}" end get '/foo' do # Matches non-songbird browsers end ほかに`host_name`と`provides`条件が利用可能です: get '/', :host_name => /^admin\./ do "Admin Area, Access denied!" end get '/', :provides => 'html' do haml :index end get '/', :provides => ['rss', 'atom', 'xml'] do builder :feed end 独自の条件を定義することも簡単にできます: set(:probability) { |value| condition { rand <= value } } get '/win_a_car', :probability => 0.1 do "You won!" end get '/win_a_car' do "Sorry, you lost." end ### 戻り値 ルートブロックの戻り値は、HTTPクライアントまたはRackスタックでの次のミドルウェアに渡されるレスポンスボディを決定します。 これは大抵の場合、上の例のように文字列ですが、それ以外の値も使用することができます。 Rackレスポンス、Rackボディオブジェクト、HTTPステータスコードのいずれかとして 妥当なオブジェクトであればどのようなオブジェクトでも返すことができます: - 3要素の配列: `[ステータス(Fixnum), ヘッダ(Hash), レスポンスボディ(#eachに応答する)]` - 2要素の配列: `[ステータス(Fixnum), レスポンスボディ(#eachに応答する)]` - `#each`に応答し、与えられたブロックに文字列を渡すオブジェクト - ステータスコードを表現するFixnum そのように、例えばストリーミングの例を簡単に実装することができます: class Stream def each 100.times { |i| yield "#{i}\n" } end end get('/') { Stream.new } ## 静的ファイル 静的ファイルは`./public`ディレクトリから配信されます。 `:public_folder`オプションを指定することで別の場所を指定することができます。 set :public_folder, File.dirname(__FILE__) + '/static' 注意: この静的ファイル用のディレクトリ名はURL中に含まれません。 例えば、`./public/css/style.css`は`http://example.com/css/style.css`でアクセスできます。 ## ビュー / テンプレート テンプレートは`./views`ディレクトリ下に配置されています。 他のディレクトリを使用する場合の例: set :views, File.dirname(__FILE__) + '/templates' テンプレートはシンボルを使用して参照させることを覚えておいて下さい。 サブデレクトリでもこの場合は`:'subdir/template'`のようにします。 レンダリングメソッドは文字列が渡されると、そのまま文字列を出力します。 ### Haml テンプレート hamlを使うにはhamlライブラリが必要です: # hamlを読み込みます require 'haml' get '/' do haml :index end `./views/index.haml`を表示します。 [Haml’s options](http://haml.info/docs/yardoc/file.HAML_REFERENCE.html#options) はSinatraの設定でグローバルに設定することができます。 [Options and Configurations](http://www.sinatrarb.com/configuration.html), を参照してそれぞれ設定を上書きして下さい。 set :haml, {:format => :html5 } # デフォルトのフォーマットは:xhtml get '/' do haml :index, :haml_options => {:format => :html4 } # 上書き end ### Erb テンプレート # erbを読み込みます require 'erb' get '/' do erb :index end `./views/index.erb`を表示します。 ### Erubis erubisテンプレートを表示するには、erubisライブラリが必要です: # erubisを読み込みます require 'erubis' get '/' do erubis :index end `./views/index.erubis`を表示します。 ### Builder テンプレート builderを使うにはbuilderライブラリが必要です: # builderを読み込みます require 'builder' get '/' do builder :index end `./views/index.builder`を表示します。 ### 鋸 テンプレート 鋸を使うには鋸ライブラリが必要です: # 鋸を読み込みます require 'nokogiri' get '/' do nokogiri :index end `./views/index.nokogiri`を表示します。 ### Sass テンプレート Sassテンプレートを使うにはsassライブラリが必要です: # hamlかsassを読み込みます require 'sass' get '/stylesheet.css' do sass :stylesheet end `./views/stylesheet.sass`を表示します。 [Sass’ options](http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#options) はSinatraの設定でグローバルに設定することができます。 see [Options and Configurations](http://www.sinatrarb.com/configuration.html), を参照してそれぞれ設定を上書きして下さい。 set :sass, {:style => :compact } # デフォルトのSass styleは :nested get '/stylesheet.css' do sass :stylesheet, :sass_options => {:style => :expanded } # 上書き end ### Scss テンプレート Scssテンプレートを使うにはsassライブラリが必要です: # hamlかsassを読み込みます require 'sass' get '/stylesheet.css' do scss :stylesheet end `./views/stylesheet.scss`を表示します。 [Sass’ options](http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#options) はSinatraの設定でグローバルに設定することができます。 see [Options and Configurations](http://www.sinatrarb.com/configuration.html), を参照してそれぞれ設定を上書きして下さい。 set :scss, :style => :compact # デフォルトのScss styleは:nested get '/stylesheet.css' do scss :stylesheet, :style => :expanded # 上書き end ### Less テンプレート Lessテンプレートを使うにはlessライブラリが必要です: # lessを読み込みます require 'less' get '/stylesheet.css' do less :stylesheet end `./views/stylesheet.less`を表示します。 ### Liquid テンプレート Liquidテンプレートを使うにはliquidライブラリが必要です: # liquidを読み込みます require 'liquid' get '/' do liquid :index end `./views/index.liquid`を表示します。 LiquidテンプレートからRubyのメソッド(`yield`を除く)を呼び出すことができないため、 ほぼ全ての場合にlocalsを指定する必要があるでしょう: liquid :index, :locals => { :key => 'value' } ### Markdown テンプレート Markdownテンプレートを使うにはrdiscountライブラリが必要です: # rdiscountを読み込みます require "rdiscount" get '/' do markdown :index end `./views/index.markdown`を表示します。(`md`と`mkd`も妥当な拡張子です) markdownからメソッドを呼び出すことも、localsに変数を渡すこともできません。 それゆえ、他のレンダリングエンジンとの組み合わせで使うのが普通です: erb :overview, :locals => { :text => markdown(:introduction) } 他のテンプレートからmarkdownメソッドを呼び出してもよいことに注意してください: %h1 Hello From Haml! %p= markdown(:greetings) ### Textile テンプレート Textileテンプレートを使うにはRedClothライブラリが必要です: # redclothを読み込みます require "redcloth" get '/' do textile :index end `./views/index.textile`を表示します。 textileからメソッドを呼び出すことも、localsに変数を渡すこともできません。 それゆえ、他のレンダリングエンジンとの組み合わせで使うのが普通です: erb :overview, :locals => { :text => textile(:introduction) } 他のテンプレートからtextileメソッドを呼び出してもよいことに注意してください: %h1 Hello From Haml! %p= textile(:greetings) ### RDoc テンプレート RDocテンプレートを使うにはRDocライブラリが必要です: # rdoc/markup/to_htmlを読み込みます require "rdoc" require "rdoc/markup/to_html" get '/' do rdoc :index end `./views/index.rdoc`を表示します。 rdocからメソッドを呼び出すことも、localsに変数を渡すこともできません。 それゆえ、他のレンダリングエンジンとの組み合わせで使うのが普通です: erb :overview, :locals => { :text => rdoc(:introduction) } 他のテンプレートからrdocメソッドを呼び出してもよいことに注意してください: %h1 Hello From Haml! %p= rdoc(:greetings) ### Radius テンプレート Radiusテンプレートを使うにはradiusライブラリが必要です: # radiusを読み込みます require 'radius' get '/' do radius :index end `./views/index.radius`を表示します。 RadiusテンプレートからRubyのメソッド(`yield`を除く)を呼び出すことができないため、 ほぼ全ての場合にlocalsを指定する必要があるでしょう: radius :index, :locals => { :key => 'value' } ### Markaby テンプレート Markabyテンプレートを使うにはmarkabyライブラリが必要です: # markabyを読み込みます require 'markaby' get '/' do markaby :index end `./views/index.mab`を表示します。 ### RABL テンプレート RABLテンプレートを使うにはrablライブラリが必要です: # rablを読み込みます require 'rabl' get '/' do rabl :index end `./views/index.rabl`を表示します。 ### Slim テンプレート Slimテンプレートを使うにはslimライブラリが必要です: # slimを読み込みます require 'slim' get '/' do slim :index end `./views/index.slim`を表示します。 ### Creole テンプレート Creoleテンプレートを使うにはcreoleライブラリが必要です: # creoleを読み込みます require 'creole' get '/' do creole :index end `./views/index.creole`を表示します。 ### CoffeeScript テンプレート CoffeeScriptテンプレートを表示するにはcoffee-scriptライブラリと\`coffee\`バイナリが必要です: # coffee-scriptを読み込みます require 'coffee-script' get '/application.js' do coffee :application end `./views/application.coffee`を表示します。 ### インラインテンプレート get '/' do haml '%div.title Hello World' end 文字列をテンプレートとして表示します。 ### テンプレート内で変数にアクセスする テンプレートはルートハンドラと同じコンテキストの中で評価されます。. ルートハンドラでセットされたインスタンス変数は テンプレート内で直接使うことができます。 get '/:id' do @foo = Foo.find(params[:id]) haml '%h1= @foo.name' end ローカル変数を明示的に定義することもできます。 get '/:id' do foo = Foo.find(params[:id]) haml '%h1= foo.name', :locals => { :foo => foo } end このやり方は他のテンプレート内で部分テンプレートとして表示する時に典型的に使用されます。 ### ファイル内テンプレート テンプレートはソースファイルの最後で定義することもできます。 require 'rubygems' require 'sinatra' get '/' do haml :index end __END__ @@ layout %html = yield @@ index %div.title Hello world!!!!! 注意: sinatraをrequireするファイル内で定義されたファイル内テンプレートは自動的に読み込まれます。 他のファイルで定義されているテンプレートを使うには `enable :inline_templates`を明示的に呼んでください。 ### 名前付きテンプレート テンプレートはトップレベルの`template`メソッドで定義することができます。 template :layout do "%html\n =yield\n" end template :index do '%div.title Hello World!' end get '/' do haml :index end 「layout」というテンプレートが存在する場合、そのテンプレートファイルは他のテンプレートが 表示される度に使用されます。`:layout => false`することでlayoutsを無効にできます。 get '/' do haml :index, :layout => !request.xhr? end ## ヘルパー トップレベルの`helpers`を使用してルートハンドラやテンプレートで使うヘルパメソッドを 定義できます。 helpers do def bar(name) "#{name}bar" end end get '/:name' do bar(params[:name]) end ## フィルタ beforeフィルタはリクエストされたコンテキストを実行する前に評価され、 リクエストとレスポンスを変更することができます。フィルタ内でセットされた インスタンス変数はルーティングとテンプレートで使用できます。 before do @note = 'Hi!' request.path_info = '/foo/bar/baz' end get '/foo/*' do @note #=> 'Hi!' params[:splat] #=> 'bar/baz' end afterフィルタは同じコンテキストにあるリクエストの後に評価され、 同じくリクエストとレスポンスを変更することができます。 beforeフィルタとルートで設定されたインスタンス変数は、 afterフィルタからアクセスすることができます: after do puts response.status end フィルタにはオプションとしてパターンを渡すことができ、 この場合はリクエストのパスがパターンにマッチした場合のみフィルタが評価されます: before '/protected/*' do authenticate! end after '/create/:slug' do |slug| session[:last_slug] = slug end ## 強制終了 ルートかbeforeフィルタ内で直ちに実行を終了する方法: halt ステータスを指定することができます: halt 410 body部を指定することもできます … halt 'ここにbodyを書く' ステータスとbody部を指定する … halt 401, '立ち去れ!' ヘッダを指定: halt 402, {'Content-Type' => 'text/plain'}, 'リベンジ' ## パッシング(Passing) ルートは`pass`を使って次のルートに飛ばすことができます: get '/guess/:who' do pass unless params[:who] == 'Frank' "見つかっちゃった!" end get '/guess/*' do "はずれです!" end ルートブロックからすぐに抜け出し、次にマッチするルートを実行します。 マッチするルートが見当たらない場合は404が返されます。 ## リクエストオブジェクトへのアクセス 受信するリクエストオブジェクトは、\`request\`メソッドを通じてリクエストレベル(フィルタ、ルート、エラーハンドラ)からアクセスすることができます: # アプリケーションが http://example.com/example で動作している場合 get '/foo' do request.body # クライアントによって送信されたリクエストボディ(下記参照) request.scheme # "http" request.script_name # "/example" request.path_info # "/foo" request.port # 80 request.request_method # "GET" request.query_string # "" request.content_length # request.bodyの長さ request.media_type # request.bodyのメディアタイプ request.host # "example.com" request.get? # true (他の動詞についても同様のメソッドあり) request.form_data? # false request["SOME_HEADER"] # SOME_HEADERヘッダの値 request.referer # クライアントのリファラまたは'/' request.user_agent # ユーザエージェント (:agent 条件によって使用される) request.cookies # ブラウザクッキーのハッシュ request.xhr? # Ajaxリクエストかどうか request.url # "http://example.com/example/foo" request.path # "/example/foo" request.ip # クライアントのIPアドレス request.secure? # false request.env # Rackによって渡された生のenvハッシュ end `script_name`や`path_info`などのオプションは次のように利用することもできます: before { request.path_info = "/" } get "/" do "全てのリクエストはここに来る" end `request.body`はIOまたはStringIOのオブジェクトです: post "/api" do request.body.rewind # 既に読まれているときのため data = JSON.parse request.body.read "Hello #{data['name']}!" end ## 設定 どの環境でも起動時に1回だけ実行されます。 configure do ... end 環境(RACK\_ENV環境変数)が`:production`に設定されている時だけ実行する方法: configure :production do ... end 環境が`:production`か`:test`の場合に設定する方法: configure :production, :test do ... end ## エラーハンドリング エラーハンドラーはルートコンテキストとbeforeフィルタ内で実行します。 `haml`、`erb`、`halt`などを使うこともできます。 ### Not Found `Sinatra::NotFound`が起きた時か レスポンスのステータスコードが 404の時に`not_found`ハンドラーが発動します。 not_found do 'ファイルが存在しません' end ### エラー `error` ハンドラーはルートブロックかbeforeフィルタ内で例外が発生した時はいつでも発動します。 例外オブジェクトはRack変数`sinatra.error`から取得できます。 error do 'エラーが発生しました。 - ' + env['sinatra.error'].name end エラーをカスタマイズする場合は、 error MyCustomError do 'エラーメッセージ...' + env['sinatra.error'].message end と書いておいて,下記のように呼び出します。 get '/' do raise MyCustomError, '何かがまずかったようです' end そうするとこうなります: エラーメッセージ... 何かがまずかったようです あるいは、ステータスコードに対応するエラーハンドラを設定することもできます: error 403 do 'Access forbidden' end get '/secret' do 403 end 範囲指定もできます: error 400..510 do 'Boom' end 開発環境として実行している場合、Sinatraは特別な`not_found`と`error`ハンドラーを インストールしています。 ## MIMEタイプ `send_file`か静的ファイルを使う時、Sinatraが理解でいないMIMEタイプがある場合があります。 その時は `mime_type` を使ってファイル拡張子毎に登録して下さい。 mime_type :foo, 'text/foo' これはcontent\_typeヘルパで利用することができます: content_type :foo ## Rackミドルウェア [SinatraはRack](http://rack.rubyforge.org/)フレームワーク用の 最小限の標準インターフェース 上で動作しています。Rack中でもアプリケーションデベロッパー 向けに一番興味深い機能はミドルウェア(サーバとアプリケーション間に介在し、モニタリング、HTTPリクエストとレスポンス の手動操作ができるなど、一般的な機能のいろいろなことを提供するもの)をサポートすることです。 Sinatraではトップレベルの`use` メソッドを使ってRackにパイプラインを構築します。 require 'sinatra' require 'my_custom_middleware' use Rack::Lint use MyCustomMiddleware get '/hello' do 'Hello World' end `use` [Rack::Builder](http://rack.rubyforge.org/doc/classes/Rack/Builder.html) DSLで定義されていることと全て一致します。 例えば `use` メソッドはブロック構文のように複数の引数を受け取ることができます。 use Rack::Auth::Basic do |username, password| username == 'admin' && password == 'secret' end Rackはログ、デバッギング、URLルーティング、認証、セッションなどいろいろな機能を備えた標準的ミドルウェアです。 Sinatraはその多くのコンポーネントを自動で使うよう基本設定されているため、`use`で明示的に指定する必要はありません。 ## テスト SinatraでのテストはRack-basedのテストライブラリかフレームワークを使って書くことができます。 [Rack::Test](http://gitrdoc.com/brynary/rack-test) をおすすめします。やり方: require 'my_sinatra_app' require 'rack/test' class MyAppTest < Test::Unit::TestCase include Rack::Test::Methods def app Sinatra::Application end def test_my_default get '/' assert_equal 'Hello World!', last_response.body end def test_with_params get '/meet', :name => 'Frank' assert_equal 'Hello Frank!', last_response.body end def test_with_rack_env get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' assert_equal "あなたはSongbirdを使ってますね!", last_response.body end end 注意: ビルトインのSinatra::TestモジュールとSinatra::TestHarnessクラスは 0.9.2リリース以降、廃止予定になっています。 ## Sinatra::Base - ミドルウェア、ライブラリ、 モジュラーアプリ トップレベル(グローバル領域)上でいろいろ定義していくのは軽量アプリならうまくいきますが、 RackミドルウェアやRails metal、サーバのコンポーネントを含んだシンプルな ライブラリやSinatraの拡張プログラムを考慮するような場合はそうとは限りません。 トップレベルのDSLがネームスペースを汚染したり、設定を変えてしまうこと(例:./publicや./view)がありえます。 そこでSinatra::Baseの出番です。 require 'sinatra/base' class MyApp < Sinatra::Base set :sessions, true set :foo, 'bar' get '/' do 'Hello world!' end end このMyAppは独立したRackコンポーネントで、RackミドルウェアやRackアプリケーション Rails metalとして使用することができます。`config.ru`ファイル内で `use` か、または `run` でこのクラスを指定するか、ライブラリとしてサーバコンポーネントをコントロールします。 MyApp.run! :host => 'localhost', :port => 9090 Sinatra::Baseのサブクラスで使えるメソッドはトップレベルのDSLを経由して確実に使うことができます。 ほとんどのトップレベルで記述されたアプリは、以下の2点を修正することでSinatra::Baseコンポーネントに変えることができます。 - `sinatra`の代わりに`sinatra/base`を読み込む (そうしない場合、SinatraのDSLメソッドの全てがメインネームスペースにインポートされます) - ルート、エラーハンドラー、フィルター、オプションをSinatra::Baseのサブクラスに書く `Sinatra::Base` はまっさらです。ビルトインサーバを含む、ほとんどのオプションがデフォルト で無効になっています。オプション詳細については[Options and Configuration](http://sinatra.github.com/configuration.html) をご覧下さい。 補足: SinatraのトップレベルDSLはシンプルな委譲(delgation)システムで実装されています。 `Sinatra::Application`クラス(Sinatra::Baseの特別なサブクラス)は、トップレベルに送られる :get、 :put、 :post、:delete、 :before、:error、:not\_found、 :configure、:set messagesのこれら 全てを受け取ります。 詳細を閲覧されたい方はこちら(英語): [Sinatra::Delegator mixin](http://github.com/sinatra/sinatra/blob/master/lib/sinatra/base.rb#L1064) [included into the main namespace](http://github.com/sinatra/sinatra/blob/master/lib/sinatra/main.rb#L25). ### Sinatraをミドルウェアとして利用する Sinatraは他のRackミドルウェアを利用することができるだけでなく、 全てのSinatraアプリケーションは、それ自体ミドルウェアとして別のRackエンドポイントの前に追加することが可能です。 このエンドポイントには、別のSinatraアプリケーションまたは他のRackベースのアプリケーション(Rails/Ramaze/Camping/…)が用いられるでしょう。 require 'sinatra/base' class LoginScreen < Sinatra::Base enable :sessions get('/login') { haml :login } post('/login') do if params[:name] = 'admin' and params[:password] = 'admin' session['user_name'] = params[:name] else redirect '/login' end end end class MyApp < Sinatra::Base # middleware will run before filters use LoginScreen before do unless session['user_name'] halt "Access denied, please login." end end get('/') { "Hello #{session['user_name']}." } end ## スコープとバインディング 現在のスコープはどのメソッドや変数が利用可能かを決定します。 ### アプリケーション/クラスのスコープ 全てのSinatraアプリケーションはSinatra::Baseのサブクラスに相当します。 もしトップレベルDSLを利用しているならば(`require 'sinatra'`)このクラスはSinatra::Applicationであり、 そうでなければ、あなたが明示的に作成したサブクラスです。 クラスレベルでは\`get\`や\`before\`のようなメソッドを持っています。 しかし\`request\`オブジェクトや\`session\`には、全てのリクエストのために1つのアプリケーションクラスが存在するためアクセスできません。 \`set\`によって作られたオプションはクラスレベルのメソッドです: class MyApp < Sinatra::Base # Hey, I'm in the application scope! set :foo, 42 foo # => 42 get '/foo' do # Hey, I'm no longer in the application scope! end end 次の場所ではアプリケーションスコープバインディングを持ちます: - アプリケーションのクラス本体 - 拡張によって定義されたメソッド - \`helpers\`に渡されたブロック - \`set\`の値として使われるProcまたはブロック このスコープオブジェクト(クラス)は次のように利用できます: - configureブロックに渡されたオブジェクト経由(`configure { |c| ... }`) - リクエストスコープの中での\`settings\` ### リクエスト/インスタンスのスコープ やってくるリクエストごとに、あなたのアプリケーションクラスの新しいインスタンスが作成され、全てのハンドラブロックがそのスコープで実行されます。 このスコープの内側からは\`request\`や\`session\`オブジェクトにアクセスすることができ、\`erb\`や\`haml\`のような表示メソッドを呼び出すことができます。 リクエストスコープの内側からは、\`settings\`ヘルパによってアプリケーションスコープにアクセスすることができます。 class MyApp < Sinatra::Base # Hey, I'm in the application scope! get '/define_route/:name' do # Request scope for '/define_route/:name' @value = 42 settings.get("/#{params[:name]}") do # Request scope for "/#{params[:name]}" @value # => nil (not the same request) end "Route defined!" end end 次の場所ではリクエストスコープバインディングを持ちます: - get/head/post/put/delete ブロック - before/after フィルタ - helper メソッド - テンプレート/ビュー ### デリゲートスコープ デリゲートスコープは、単にクラススコープにメソッドを転送します。 しかしながら、クラスのバインディングを持っていないため、クラススコープと全く同じふるまいをするわけではありません: 委譲すると明示的に示されたメソッドのみが利用可能であり、またクラススコープと変数/状態を共有することはできません(注: 異なった\`self\`を持っています)。 `Sinatra::Delegator.delegate :method_name`を呼び出すことによってデリゲートするメソッドを明示的に追加することができます。 次の場所ではデリゲートスコープを持ちます: - もし`require "sinatra"`しているならば、トップレベルバインディング - \`Sinatra::Delegator\` mixinでextendされたオブジェクト コードをご覧ください: ここでは [Sinatra::Delegator mixin](http://github.com/sinatra/sinatra/blob/ceac46f0bc129a6e994a06100aa854f606fe5992/lib/sinatra/base.rb#L1128) は[main 名前空間にincludeされています](http://github.com/sinatra/sinatra/blob/ceac46f0bc129a6e994a06100aa854f606fe5992/lib/sinatra/main.rb#L28). ## コマンドライン Sinatraアプリケーションは直接実行できます。 ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER] オプション: -h # ヘルプ -p # ポート指定(デフォルトは4567) -o # ホスト指定(デフォルトは0.0.0.0) -e # 環境を指定 (デフォルトはdevelopment) -s # rackserver/handlerを指定 (デフォルトはthin) -x # mutex lockを付ける (デフォルトはoff) ## 最新開発版について Sinatraの開発版を使いたい場合は、ローカルに開発版を落として、 `LOAD_PATH`の`sinatra/lib`ディレクトリを指定して実行して下さい。 cd myapp git clone git://github.com/sinatra/sinatra.git ruby -Isinatra/lib myapp.rb `sinatra/lib`ディレクトリをアプリケーションの`LOAD_PATH`に追加する方法もあります。 $LOAD_PATH.unshift File.dirname(__FILE__) + '/sinatra/lib' require 'rubygems' require 'sinatra' get '/about' do "今使ってるバージョンは" + Sinatra::VERSION end Sinatraのソースを更新する方法: cd myproject/sinatra git pull ## その他 日本語サイト - [Greenbear Laboratory Rack日本語マニュアル](http://mono.kmc.gr.jp/~yhara/w/?RackReferenceJa) - Rackの日本語マニュアル 英語サイト - [プロジェクトサイト](http://sinatra.github.com/) - ドキュメント、 ニュース、他のリソースへのリンクがあります。 - [プロジェクトに参加(貢献)する](http://sinatra.github.com/contributing.html) - バグレポート パッチの送信、サポートなど - [Issue tracker](http://github.com/sinatra/sinatra/issues) - チケット管理とリリース計画 - [Twitter](http://twitter.com/sinatra) - [メーリングリスト](http://groups.google.com/group/sinatrarb) - [IRC: \#sinatra](irc://chat.freenode.net/#sinatra) on [freenode.net](http://freenode.net)sinatra-1.4.3/README.es.md0000644000004100000410000021151312161612727015105 0ustar www-datawww-data# Sinatra *Atención: Este documento es una traducción de la versión en inglés y puede estar desactualizado.* Sinatra es un [DSL](http://es.wikipedia.org/wiki/Lenguaje_específico_del_dominio) para crear aplicaciones web rápidamente en Ruby con un mínimo esfuerzo: ``` ruby # miapp.rb require 'sinatra' get '/' do 'Hola mundo!' end ``` Instalá el gem y corré la aplicación con: ``` shell gem install sinatra ruby miapp.rb ``` Podés verla en: http://localhost:4567 Es recomendable además ejecutar `gem install thin`, ya que Sinatra lo va a utilizar cuando esté disponible. ## Rutas En Sinatra, una ruta está compuesta por un método HTTP y un patrón de una URL. Cada ruta se asocia con un bloque: ``` ruby get '/' do .. mostrar algo .. end post '/' do .. crear algo .. end put '/' do .. reemplazar algo .. end patch '/' do .. modificar algo .. end delete '/' do .. aniquilar algo .. end options '/' do .. informar algo .. end ``` Las rutas son comparadas en el orden en el que son definidas. La primera ruta que coincide con la petición es invocada. Los patrones de las rutas pueden incluir parámetros nombrados, accesibles a través del hash `params`: ``` ruby get '/hola/:nombre' do # coincide con "GET /hola/foo" y "GET /hola/bar" # params[:nombre] es 'foo' o 'bar' "Hola #{params[:nombre]}!" end ``` También podés acceder a los parámetros nombrados usando parámetros de bloque: ``` ruby get '/hola/:nombre' do |n| # coincide con "GET /hola/foo" y "GET /hola/bar" # params[:nombre] es 'foo' o 'bar' # n almacena params[:nombre] "Hola #{n}!" end ``` Los patrones de ruta también pueden incluir parámetros splat (o wildcard), accesibles a través del arreglo `params[:splat]`: ``` ruby get '/decir/*/al/*' do # coincide con /decir/hola/al/mundo params[:splat] # => ["hola", "mundo"] end get '/descargar/*.*' do # coincide con /descargar/path/al/archivo.xml params[:splat] # => ["path/al/archivo", "xml"] end ``` O, con parámetros de bloque: ``` ruby get '/descargar/*.*' do |path, ext| [path, ext] # => ["path/al/archivo", "xml"] end ``` Rutas con Expresiones Regulares: ``` ruby get %r{/hola/([\w]+)} do "Hola, #{params[:captures].first}!" end ``` O con un parámetro de bloque: ``` ruby get %r{/hola/([\w]+)} do |c| "Hola, #{c}!" end ``` Los patrones de ruta pueden contener parámetros opcionales: ``` ruby 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 desactives la protección para el ataque *path traversal* (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 ejemplo el user agent: ``` ruby get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do "Estás usando la versión de Songbird #{params[:agent][0]}" end get '/foo' do # Coincide con browsers que no sean songbird end ``` Otras condiciones disponibles son `host_name` y `provides`: ``` ruby get '/', :host_name => /^admin\./ do "Área de Administración, Acceso denegado!" end get '/', :provides => 'html' do haml :index end get '/', :provides => ['rss', 'atom', 'xml'] do builder :feed end ``` Podés definir tus propias condiciones fácilmente: ``` ruby set(:probabilidad) { |valor| condition { rand <= valor } } get '/gana_un_auto', :probabilidad => 0.1 do "Ganaste!" end get '/gana_un_auto' do "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: ``` ruby 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 que determina al menos el cuerpo de la respuesta que se le pasa al cliente HTTP o al siguiente middleware en la pila de Rack. Lo más común es que sea un string, como en los ejemplos anteriores. Sin embargo, otros valores también son aceptados. Podés devolver cualquier objeto que sea una respuesta Rack válida, un objeto que represente el cuerpo de una respuesta Rack o un código de estado HTTP: * Un arreglo con tres elementos: `[estado (Fixnum), cabeceras (Hash), cuerpo de la respuesta (responde a #each)]` * Un arreglo con dos elementos: `[estado (Fixnum), cuerpo de la respuesta (responde a #each)]` * Un objeto que responde a `#each` y que le pasa únicamente strings al bloque dado * Un Fixnum representando el código de estado De esa manera podemos, por ejemplo, implementar fácilmente un streaming: ``` ruby class Stream def each 100.times { |i| yield "#{i}\n" } end end get('/') { Stream.new } ``` ### Comparadores de Rutas Personalizados Como se mostró anteriormente, Sinatra permite utilizar Strings y expresiones regulares para definir las rutas. Sin embargo, la cosa no termina ahí. Podés definir tus propios comparadores muy fácilmente: ``` ruby class PattronCualquieraMenos Match = Struct.new(:captures) def initialize(excepto) @excepto = excepto @capturas = Match.new([]) end def match(str) @capturas unless @excepto === str end end def cualquiera_menos(patron) PatronCualquieraMenos.new(patron) end get cualquiera_menos("/index") do # ... end ``` Tené en cuenta que el ejemplo anterior es un poco rebuscado. Un resultado similar puede conseguirse más sencillamente: ``` ruby get // do pass if request.path_info == "/index" # ... end ``` O, usando un lookahead negativo: ``` ruby get %r{^(?!/index$)} do # ... end ``` ### Archivos Estáticos Los archivos estáticos son servidos desde el directorio público `./public`. Podés especificar una ubicación diferente ajustando la opción `:public_folder`: ``` ruby set :public_folder, File.dirname(__FILE__) + '/estaticos' ``` Notá que el nombre del directorio público no está incluido en la URL. Por ejemplo, el archivo `./public/css/style.css` se accede a través de `http://ejemplo.com/css/style.css`. Usá la configuración `:static_cache_control` para agregar el encabezado `Cache-Control` (ver la sección de configuración para más detalles). ### Vistas / Plantillas Cada lenguaje de plantilla se expone a través de un método de renderizado que lleva su nombre. Estos métodos simplemente devuelven un string: ``` ruby get '/' do erb :index end ``` Renderiza `views/index.erb`. En lugar del nombre de la plantilla podés proporcionar directamente el contenido de la misma: ``` ruby get '/' do codigo = "<%= Time.now %>" erb codigo end ``` Los métodos de renderizado, aceptan además un segundo argumento, el hash de opciones: ``` ruby get '/' do erb :index, :layout => :post end ``` Renderiza `views/index.erb` embebido en `views/post.erb` (por defecto, la plantilla `:index` es embebida en `views/layout.erb` siempre y cuando este último archivo exista). Cualquier opción que Sinatra no entienda le será pasada al motor de renderizado de la plantilla: ``` ruby get '/' do haml :index, :format => :html5 end ``` Además podés definir las opciones para un lenguaje de plantillas de forma general: ``` ruby set :haml, :format => :html5 get '/' do haml :index end ``` Las opciones pasadas al método de renderizado tienen precedencia sobre las definidas mediante `set`. Opciones disponibles:
locals
Lista de variables locales pasadas al documento. Resultan muy útiles cuando se combinan con parciales. Ejemplo: erb "<%= foo %>", :locals => {:foo => "bar"}
default_encoding
Encoding utilizado cuando el de un string es dudoso. Por defecto toma el valor de settings.default_encoding.
views
Directorio desde donde se cargan las vistas. Por defecto toma el valor de settings.views.
layout
Si es true o false indica que se debe usar, o no, un layout, respectivamente. También puede ser un símbolo que especifique qué plantilla usar. Ejemplo: erb :index, :layout => !request.xhr?
content_type
Content-Type que produce la plantilla. El valor por defecto depende de cada lenguaje de plantillas.
scope
Ámbito en el que se renderiza la plantilla. Por defecto utiliza la instancia de la aplicación. Tené en cuenta que si cambiás esta opción las variables de instancia y los helpers van a dejar de estar disponibles.
layout_engine
Motor de renderizado de plantillas que usa para el layout. Resulta conveniente para lenguajes que no soportan layouts. Por defecto toma el valor del motor usado para renderizar la plantilla. Ejemplo: set :rdoc, :layout_engine => :erb
Se asume que las plantillas están ubicadas directamente bajo el directorio ./views. Para usar un directorio de vistas diferente: set :views, settings.root + '/plantillas'
Es importante acordarse que siempre tenés que referenciar a las plantillas con símbolos, incluso cuando se encuentran en un subdirectorio (en este caso tenés que usar: `:'subdir/plantilla'` o `'subdir/plantilla'.to_sym`). Tenés que usar un símbolo porque los métodos de renderización van a renderizar directamente cualquier string que se les pase como argumento.
### Lenguajes de Plantillas Disponibles Algunos lenguajes tienen varias implementaciones. Para especificar que implementación usar (y para ser thread-safe), deberías requerirla antes de usarla: ``` ruby require 'rdiscount' # o require 'bluecloth' get('/') { markdown :index } ``` ### Plantillas Haml
Dependencias haml
Expresiones de Archivo .haml
Ejemplo haml :index, :format => :html5
### Plantillas Erb
Dependencias erubis o erb (incluida en Ruby)
Extensiones de Archivo .erb, .rhtml o .erubis (solamente con Erubis)
Ejemplo erb :index
### Plantillas Builder
Dependencias builder
Extensiones de Archivo .builder
Ejemplo builder { |xml| xml.em "hola" }
Además, acepta un bloque con la definición de la plantilla (ver el ejemplo). ### Plantillas Nokogiri
Dependencias nokogiri
Extensiones de Archivo .nokogiri
Ejemplo nokogiri { |xml| xml.em "hola" }
Además, acepta un bloque con la definición de la plantilla (ver el ejemplo). ### Plantillas Sass
Dependencias sass
Extensiones de Archivo .sass
Ejemplo sass :stylesheet, :style => :expanded
### Plantillas SCSS
Dependencias sass
Extensiones de Archivo .scss
Ejemplo scss :stylesheet, :style => :expanded
### Plantillas Less
Dependencias less
Extensiones de Archivo .less
Ejemplo less :stylesheet
### Plantillas Liquid
Dependencias liquid
Extensiones de Archivo .liquid
Ejemplo liquid :index, :locals => { :clave => 'valor' }
Como no vas a poder llamar a métodos de Ruby (excepto por `yield`) desde una plantilla Liquid, casi siempre vas a querer pasarle locales. ### Plantillas Markdown
Dependencias RDiscount, RedCarpet, BlueCloth, kramdown o maruku
Extensiones de Archivo .markdown, .mkd y .md
Ejemplo markdown :index, :layout_engine => :erb
No es posible llamar métodos desde markdown, ni pasarle locales. Por lo tanto, generalmente vas a usarlo en combinación con otro motor de renderizado: ``` ruby erb :resumen, :locals => { :texto => markdown(:introduccion) } ``` Tené en cuenta que también podés llamar al método `markdown` desde otras plantillas: ``` ruby %h1 Hola Desde Haml! %p= markdown(:saludos) ``` Como no podés utilizar Ruby desde Markdown, no podés usar layouts escritos en Markdown. De todos modos, es posible usar un motor de renderizado para el layout distinto al de la plantilla pasando la opción `:layout_engine`. ### Plantillas Textile
Dependencias RedCloth
Extensiones de Archivo .textile
Ejemplo textile :index, :layout_engine => :erb
No es posible llamar métodos desde textile, ni pasarle locales. Por lo tanto, generalmente vas a usarlo en combinación con otro motor de renderizado: ``` ruby erb :resumen, :locals => { :texto => textile(:introduccion) } ``` Tené en cuenta que también podés llamar al método `textile` desde otras plantillas: ``` ruby %h1 Hola Desde Haml! %p= textile(:saludos) ``` Como no podés utilizar Ruby desde Textile, no podés usar layouts escritos en Textile. De todos modos, es posible usar un motor de renderizado para el layout distinto al de la plantilla pasando la opción `:layout_engine`. ### Plantillas RDoc
Dependencias RDoc
Extensiones de Archivo .rdoc
Ejemplo rdoc :README, :layout_engine => :erb
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: ``` ruby erb :resumen, :locals => { :texto => rdoc(:introduccion) } ``` Tené en cuenta que también podés llamar al método `rdoc` desde otras plantillas: ``` ruby %h1 Hola Desde Haml! %p= rdoc(:saludos) ``` Como no podés utilizar Ruby desde RDoc, no podés usar layouts escritos en RDoc. De todos modos, es posible usar un motor de renderizado para el layout distinto al de la plantilla pasando la opción `:layout_engine`. ### Plantillas Radius
Dependencias Radius
Extensiones de Archivo .radius
Ejemplo radius :index, :locals => { :clave => 'valor' }
Como no vas a poder llamar a métodos de Ruby (excepto por `yield`) desde una plantilla Radius, casi siempre vas a querer pasarle locales. ### Plantillas Markaby
Dependencias Markaby
Extensiones de Archivo .mab
Ejemplo markaby { h1 "Bienvenido!" }
Además, acepta un bloque con la definición de la plantilla (ver el ejemplo). ### Plantillas RABL
Dependencias Rabl
Extensiones de Archivo .rabl
Ejemplo rabl :index
### Plantillas Slim
Dependencias Slim Lang
Extensiones de Archivo .slim
Ejemplo slim :index
### Plantillas Creole
Dependencias Creole
Extensiones de Archivo .creole
Ejemplo creole :wiki, :layout_engine => :erb
No es posible llamar métodos desde creole, ni pasarle locales. Por lo tanto, generalmente vas a usarlo en combinación con otro motor de renderizado: ``` ruby erb :resumen, :locals => { :texto => cerole(:introduccion) } ``` Tené en cuenta que también podés llamar al método `creole` desde otras plantillas: ``` ruby %h1 Hola Desde Haml! %p= creole(:saludos) ``` Como no podés utilizar Ruby desde Creole, no podés usar layouts escritos en Creloe. De todos modos, es posible usar un motor de renderizado para el layout distinto al de la plantilla pasando la opción `:layout_engine`. ### Plantillas CoffeeScript
Dependencias CoffeeScript y un mecanismo para ejecutar javascript
Extensiones de Archivo .coffee
Ejemplo coffee :index
### Plantillas Stylus
Dependencias Stylus y un mecanismo para ejecutar javascript
Extensiones de Archivo .styl
Ejemplo stylus :index
### Plantillas Yajl
Dependencias yajl-ruby
Extensiones de Archivo .yajl
Ejemplo yajl :index, :locals => { :key => 'qux' }, :callback => 'present', :variable => 'resource'
El contenido de La plantilla se evalúa como código Ruby, y la variable `json` es convertida a JSON mediante `#to_json`. ``` ruby json = { :foo => 'bar' } json[:baz] = key ``` Las opciones `:callback` y `:variable` se pueden utilizar para decorar el objeto renderizado: ``` ruby var resource = {"foo":"bar","baz":"qux"}; present(resource); ``` ### Plantillas WLang
Dependencias wlang
Extensiones de Archivo .wlang
Ejemplo wlang :index, :locals => { :clave => 'valor' }
Como no vas a poder llamar a métodos de Ruby (excepto por `yield`) desde una plantilla WLang, casi siempre vas a querer pasarle locales. ### Plantillas Embebidas ``` ruby get '/' do haml '%div.titulo Hola Mundo' end ``` Renderiza el template embebido en el string. ### Accediendo a Variables en Plantillas Las plantillas son evaluadas dentro del mismo contexto que los manejadores de ruta. Las variables de instancia asignadas en los manejadores de ruta son accesibles directamente por las plantillas: ``` ruby get '/:id' do @foo = Foo.find(params[:id]) haml '%h1= @foo.nombre' end ``` O es posible especificar un Hash de variables locales explícitamente: ``` ruby get '/:id' do foo = Foo.find(params[:id]) haml '%h1= bar.nombre', :locals => { :bar => foo } end ``` Esto es usado típicamente cuando se renderizan plantillas como parciales desde adentro de otras plantillas. ### Plantillas Inline Las plantillas pueden ser definidas al final del archivo fuente: ``` ruby require 'rubygems' require 'sinatra' get '/' do haml :index end __END__ @@ layout %html = yield @@ index %div.titulo Hola mundo!!!!! ``` NOTA: únicamente las plantillas inline definidas en el archivo fuente que requiere sinatra son cargadas automáticamente. Llamá `enable :inline_templates` explícitamente si tenés plantillas inline en otros archivos fuente. ### Plantillas Nombradas Las plantillas también pueden ser definidas usando el método top-level `template`: ``` ruby template :layout do "%html\n =yield\n" end template :index do '%div.titulo Hola Mundo!' end get '/' do haml :index end ``` Si existe una plantilla con el nombre "layout", va a ser usada cada vez que una plantilla es renderizada. Podés desactivar los layouts individualmente pasando `:layout => false` o globalmente con `set :haml, :layout => false`: ``` ruby get '/' do haml :index, :layout => !request.xhr? end ``` ### Asociando Extensiones de Archivo Para asociar una extensión de archivo con un motor de renderizado, usá `Tilt.register`. Por ejemplo, si querés usar la extensión `tt` para las plantillas Textile, podés hacer lo siguiente: ``` ruby Tilt.register :tt, Tilt[:textile] ``` ### Agregando Tu Propio Motor de Renderizado Primero, registrá tu motor con Tilt, y después, creá tu método de renderizado: ``` ruby Tilt.register :mipg, MiMotorParaPlantillaGenial helpers do def mypg(*args) render(:mypg, *args) end end get '/' do mypg :index end ``` Renderiza `./views/index.mypg`. Mirá https://github.com/rtomayko/tilt para aprender más de Tilt. ## Filtros Los filtros `before` son evaluados antes de cada petición dentro del mismo contexto que las rutas. Pueden modificar la petición y la respuesta. Las variables de instancia asignadas en los filtros son accesibles por las rutas y las plantillas: ``` ruby before do @nota = 'Hey!' request.path_info = '/foo/bar/baz' end get '/foo/*' do @nota #=> 'Hey!' params[:splat] #=> 'bar/baz' end ``` Los filtros `after` son evaluados después de cada petición dentro del mismo contexto y también pueden modificar la petición y la respuesta. Las variables de instancia asignadas en los filtros `before` y en las rutas son accesibles por los filtros `after`: ``` ruby after do puts response.status end ``` Nota: A menos que uses el método `body` en lugar de simplemente devolver un string desde una ruta, el cuerpo de la respuesta no va a estar disponible en un filtro after, debido a que todavía no se ha generado. Los filtros aceptan un patrón opcional, que cuando está presente causa que los mismos sean evaluados únicamente si el path de la petición coincide con ese patrón: ``` ruby before '/protegido/*' do autenticar! end after '/crear/:slug' do |slug| session[:ultimo_slug] = slug end ``` Al igual que las rutas, los filtros también pueden aceptar condiciones: ``` ruby before :agent => /Songbird/ do # ... end after '/blog/*', :host_name => 'ejemplo.com' do # ... end ``` ## Ayudantes Usá el método top-level *helpers* para definir métodos ayudantes que pueden ser utilizados dentro de los manejadores de rutas y las plantillas: ``` ruby helpers do def bar(nombre) "#{nombre}bar" end end get '/:nombre' do bar(params[:nombre]) end ``` Por cuestiones organizativas, puede resultar conveniente organizar los métodos ayudantes en distintos módulos: ``` ruby module FooUtils def foo(nombre) "#{nombre}foo" end end module BarUtils def bar(nombre) "#{nombre}bar" end end helpers FooUtils, BarUtils ``` El efecto de utilizar *helpers* de esta manera es el mismo que resulta de incluir los módulos en la clase de la aplicación. ### Usando Sesiones Una sesión es usada para mantener el estado a través de distintas peticiones. Cuando están activadas, proporciona un hash de sesión para cada sesión de usuario: ``` ruby enable :sessions get '/' do "valor = " << session[:valor].inspect end get '/:valor' do session[:valor] = params[:valor] end ``` Tené en cuenta que `enable :sessions` guarda todos los datos en una cookie, lo cual no es siempre deseable (guardar muchos datos va a incrementar el tráfico, por citar un ejemplo). Podés usar cualquier middleware Rack para manejar sesiones, de la misma manera que usarías cualquier otro middleware, pero con la salvedad de que *no* tenés que llamar a `enable :sessions`: ``` ruby use Rack::Session::Pool, :expire_after => 2592000 get '/' do "valor = " << session[:valor].inspect end get '/:valor' do session[:valor] = params[:valor] end ``` Para incrementar la seguridad, los datos de la sesión almacenados en la cookie son firmados con un secreto de sesión. Este secreto, es generado aleatoriamente por Sinatra. De cualquier manera, hay que tener en cuenta que cada vez que inicies la aplicación se va a generar uno nuevo. Así, si querés que todas las instancias de tu aplicación compartan un único secreto, tenés que definirlo vos: ``` ruby set :session_secret, 'super secreto' ``` Si necesitás una configuración más específica, `sessions` acepta un Hash con opciones: ``` ruby set :sessions, :domain => 'foo.com' ``` ### Interrupción Para detener inmediatamente una petición dentro de un filtro o una ruta usá: ``` ruby halt ``` También podés especificar el estado: ``` ruby halt 410 ``` O el cuerpo: ``` ruby halt 'esto va a ser el cuerpo' ``` O los dos: ``` ruby halt 401, 'salí de acá!' ``` Con cabeceras: ``` ruby halt 402, { 'Content-Type' => 'text/plain' }, 'venganza' ``` Obviamente, es posible utilizar `halt` con una plantilla: ``` ruby halt erb(:error) ``` ### Paso Una ruta puede pasarle el procesamiento a la siguiente ruta que coincida con la petición usando `pass`: ``` ruby get '/adivina/:quien' do pass unless params[:quien] == 'Franco' 'Adivinaste!' end get '/adivina/*' do 'Erraste!' end ``` Se sale inmediatamente del bloque de la ruta y se le pasa el control a la siguiente ruta que coincida. Si no coincide ninguna ruta, se devuelve 404. ### Ejecutando Otra Ruta Cuando querés obtener el resultado de la llamada a una ruta, `pass` no te va a servir. Para lograr esto, podés usar `call`: ``` ruby get '/foo' do status, headers, body = call env.merge("PATH_INFO" => '/bar') [status, headers, body.map(&:upcase)] end get '/bar' do "bar" end ``` Notá que en el ejemplo anterior, es conveniente mover `"bar"` a un helper, y llamarlo desde `/foo` y `/bar`. Así, vas a simplificar las pruebas y a mejorar el rendimiento. Si querés que la petición se envíe a la misma instancia de la aplicación en lugar de a otra, usá `call!` en lugar de `call`. En la especificación de Rack podés encontrar más información sobre `call`. ### Asignando el Código de Estado, los Encabezados y el Cuerpo de una Respuesta Es posible, y se recomienda, asignar el código de estado y el cuerpo de una respuesta con el valor de retorno de una ruta. De cualquier manera, en varios escenarios, puede que sea conveniente asignar el cuerpo en un punto arbitrario del flujo de ejecución con el método `body`. A partir de ahí, podés usar ese mismo método para acceder al cuerpo de la respuesta: ``` ruby get '/foo' do body "bar" end after do puts body end ``` También es posible pasarle un bloque a `body`, que será ejecutado por el Rack handler (podés usar esto para implementar streaming, mirá "Valores de retorno"). De manera similar, también podés asignar el código de estado y encabezados: ``` ruby get '/foo' do status 418 headers \ "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: ``` ruby 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. Si estás usando Shotgun, el streaming no va a funcionar. 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: ``` ruby 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 una instancia de `Logger`: ``` ruby get '/' do logger.info "cargando datos" # ... end ``` Este logger tiene en cuenta la configuración de logueo de tu Rack handler. Si el logueo está desactivado, este método va a devolver un objeto que se comporta como un logger pero que en realidad no hace nada. Así, no vas a tener que preocuparte por esta situación. Tené en cuenta que el logueo está habilitado por defecto únicamente para `Sinatra::Application`. Si heredaste de `Sinatra::Base`, probablemente quieras habilitarlo manualmente: ``` ruby class MiApp < Sinatra::Base configure :production, :development do enable :logging end end ``` Para evitar que se inicialice cualquier middleware de logging, configurá `logging` a `nil`. Tené en cuenta que, cuando hagas esto, `logger` va a devolver `nil`. Un caso común es cuando querés usar tu propio logger. Sinatra va a usar lo que encuentre en `env['rack.logger']`. ### Tipos Mime Cuando usás `send_file` o archivos estáticos tal vez tengas tipos mime que Sinatra no entiende. Usá `mime_type` para registrarlos a través de la extensión de archivo: ``` ruby configure do mime_type :foo, 'text/foo' end ``` También lo podés usar con el ayudante `content_type`: ``` ruby get '/' do content_type :foo "foo foo foo" end ``` ### Generando URLs Para generar URLs deberías usar el método `url`. Por ejemplo, en Haml: ``` ruby %a{:href => url('/foo')} foo ``` Tiene en cuenta proxies inversos y encaminadores de Rack, si están presentes. Este método también puede invocarse mediante su alias `to` (mirá un ejemplo a continuación). ### Redirección del Navegador Podés redireccionar al navegador con el método `redirect`: ``` ruby get '/foo' do redirect to('/bar') end ``` Cualquier parámetro adicional se utiliza de la misma manera que los argumentos pasados a `halt`: ``` ruby redirect to('/bar'), 303 redirect 'http://google.com', 'te confundiste de lugar, compañero' ``` También podés redireccionar fácilmente de vuelta hacia la página desde donde vino el usuario con `redirect back`: ``` ruby get '/foo' do "hacer algo" end get '/bar' do hacer_algo redirect back end ``` Para pasar argumentos con una redirección, podés agregarlos a la cadena de búsqueda: ``` ruby redirect to('/bar?suma=42') ``` O usar una sesión: ``` ruby enable :sessions get '/foo' do session[:secreto] = 'foo' redirect to('/bar') end get '/bar' do session[:secreto] end ``` ### Cache Control Asignar tus encabezados correctamente es el cimiento para realizar un cacheo HTTP correcto. Podés asignar el encabezado Cache-Control fácilmente: ``` ruby get '/' do cache_control :public "cachealo!" end ``` Pro tip: configurar el cacheo en un filtro `before`: ``` ruby before do cache_control :public, :must_revalidate, :max_age => 60 end ``` Si estás usando el helper `expires` para definir el encabezado correspondiente, `Cache-Control` se va a definir automáticamente: ``` ruby before do expires 500, :public, :must_revalidate end ``` 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é: ``` ruby get '/articulo/:id' do @articulo = Articulo.find params[:id] last_modified @articulo.updated_at etag @articulo.sha1 erb :articulo end ``` También es posible usar una [weak ETag](http://en.wikipedia.org/wiki/HTTP_ETag#Strong_and_weak_validation): ``` ruby etag @articulo.sha1, :weak ``` 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 con proxys reversos, mirá [rack-cache](https://github.com/rtomayko/rack-cache): ``` ruby require "rack/cache" require "sinatra" use Rack::Cache get '/' do cache_control :public, :max_age => 36000 sleep 5 "hola" end ``` Usá la configuración `:static_cache_control` para agregar el encabezado `Cache-Control` a archivos estáticos (ver la sección de configuración para más detalles). De acuerdo con la RFC 2616 tu aplicación debería comportarse diferente si a las cabeceras If-Match o If-None-Match se le asigna el valor `*` cuando el recurso solicitado ya existe. Sinatra asume para peticiones seguras (como get) e idempotentes (como put) que el recurso existe, mientras que para el resto (como post) asume que no. Podes cambiar este comportamiento con la opción `:new_resource`: ``` ruby get '/crear' do etag '', :new_resource => true Articulo.create erb :nuevo_articulo end ``` Si querés seguir usando una weak ETag, indicalo con la opción `:kind`: ``` ruby etag '', :new_resource => true, :kind => :weak ``` ### Enviando Archivos Para enviar archivos, podés usar el método `send_file`: ``` ruby get '/' do send_file 'foo.png' end ``` Además acepta un par de opciones: ``` ruby send_file 'foo.png', :type => :jpg ``` Estas opciones son: [filename] nombre del archivo devuelto, por defecto es el nombre real del archivo. [last_modified] valor para el encabezado Last-Modified, por defecto toma el mtime del archivo. [type] el content type que se va a utilizar, si no está presente se intenta adivinar a partir de la extensión del archivo. [disposition] se utiliza para el encabezado Content-Disposition, y puede tomar alguno de los siguientes valores: `nil` (por defecto), `:attachment` e `:inline` [length] encabezado Content-Length, por defecto toma el tamaño del archivo. [status] código de estado devuelto. Resulta útil al enviar un archivo estático como una página de error. Si el Rack handler lo soporta, se intentará no transmitir directamente desde el proceso de Ruby. Si usás este método, Sinatra se va a encargar automáticamente peticiones de rango. ### Accediendo al objeto de la petición El objeto de la petición entrante puede ser accedido desde el nivel de la petición (filtros, rutas y manejadores de errores) a través del método `request`: ``` ruby # app corriendo en http://ejemplo.com/ejemplo 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 # cuerpo de la petición enviado por el cliente (ver más abajo) request.scheme # "http" request.script_name # "/ejemplo" request.path_info # "/foo" request.port # 80 request.request_method # "GET" request.query_string # "" request.content_length # longitud de request.body request.media_type # tipo de medio de request.body request.host # "ejemplo.com" request.get? # true (hay métodos análogos para los otros verbos) request.form_data? # false request["UNA_CABECERA"] # valor de la cabecera UNA_CABECERA request.referrer # la referencia del cliente o '/' request.user_agent # user agent (usado por la condición :agent) request.cookies # hash de las cookies del browser request.xhr? # es una petición ajax? request.url # "http://ejemplo.com/ejemplo/foo" request.path # "/ejemplo/foo" request.ip # dirección IP del cliente request.secure? # false (sería true sobre ssl) request.forwarded? # true (si se está corriendo atrás de un proxy reverso) requuest.env # hash de entorno directamente entregado por Rack end ``` Algunas opciones, como `script_name` o `path_info` pueden también ser escritas: ``` ruby before { request.path_info = "/" } get "/" do "todas las peticiones llegan acá" end ``` El objeto `request.body` es una instancia de IO o StringIO: ``` ruby post "/api" do request.body.rewind # en caso de que alguien ya lo haya leído datos = JSON.parse request.body.read "Hola #{datos['nombre']}!" end ``` ### Archivos Adjuntos Podés usar el método helper `attachment` para indicarle al navegador que almacene la respuesta en el disco en lugar de mostrarla en pantalla: ``` ruby get '/' do attachment "guardalo!" end ``` También podés pasarle un nombre de archivo: ``` ruby get '/' do attachment "info.txt" "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: ``` ruby 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: ``` ruby 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 `find_template` se utiliza para encontrar los archivos de las plantillas que se van a renderizar: ``` ruby find_template settings.views, 'foo', Tilt[:haml] do |archivo| puts "podría ser #{archivo}" end ``` Si bien esto no es muy útil, lo interesante es que podés sobreescribir este método, y así enganchar tu propio mecanismo de búsqueda. Por ejemplo, para poder utilizar más de un directorio de vistas: ``` ruby set :views, ['vistas', 'plantillas'] helpers do def find_template(views, name, engine, &block) Array(views).each { |v| super(v, name, engine, &block) } end end ``` Otro ejemplo consiste en usar directorios diferentes para los distintos motores de renderizado: ``` ruby set :views, :sass => 'vistas/sass', :haml => 'plantillas', :defecto => 'vistas' helpers do def find_template(views, name, engine, &block) _, folder = views.detect { |k,v| engine == Tilt[k] } folder ||= views[:defecto] super(folder, name, engine, &block) end end ``` ¡Es muy fácil convertir estos ejemplos en una extensión y compartirla! Notá que `find_template` no verifica si un archivo existe realmente, sino que llama al bloque que recibe para cada path posible. Esto no representa un problema de rendimiento debido a que `render` va a usar `break` ni bien encuentre un archivo que exista. Además, las ubicaciones de las plantillas (y su contenido) se cachean cuando no estás en el modo de desarrollo. Es bueno tener en cuenta lo anteiror si escribís un método medio loco. ## Configuración Ejecutar una vez, en el inicio, en cualquier entorno: ``` ruby configure do # asignando una opción set :opcion, 'valor' # asignando varias opciones set :a => 1, :b => 2 # atajo para `set :opcion, true` enable :opcion # atajo para `set :opcion, false` disable :opcion # también podés tener configuraciones dinámicas usando bloques set(:css_dir) { File.join(views, 'css') } end ``` Ejecutar únicamente cuando el entorno (la variable de entorno RACK_ENV) es `:production`: ``` ruby configure :production do ... end ``` Ejecutar cuando el entorno es `:production` o `:test`: ``` ruby configure :production, :test do ... end ``` Podés acceder a estas opciones utilizando el método `settings`: ``` ruby configure do set :foo, 'bar' end get '/' do settings.foo? # => true settings.foo # => 'bar' ... 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. Si por algún motivo, querés desactivar esta funcionalidad, podés hacerlo como se indica a continuación (tené en cuenta que tu aplicación va a quedar expuesta a un montón de vulnerabilidades bien conocidas): ``` ruby disable :protection ``` También es posible desactivar una única capa de defensa: ``` ruby set :protection, :except => :path_traversal ``` O varias: ``` ruby set :protection, :except => [:path_traversal, :session_hijacking] ``` ### Configuraciones Disponibles
absolute_redirects
Si está deshabilitada, Sinatra va a permitir redirecciones relativas, sin embargo, como consecuencia de esto, va a dejar de cumplir con el RFC 2616 (HTTP 1.1), que solamente permite redirecciones absolutas. Activalo si tu apliación está corriendo atrás de un proxy reverso que no se ha configurado adecuadamente. Notá que el helper url va a seguir produciendo URLs absolutas, a menos que le pasés false como segundo parámetro. Deshabilitada por defecto.
add_charsets
Tipos mime a los que el helper content_type les añade automáticamente el charset. En general, no deberías asignar directamente esta opción, sino añadirle los charsets que quieras: settings.add_charsets << "application/foobar"
app_file
Path del archivo principal de la aplicación, se utiliza para detectar la raíz del proyecto, el directorio de las vistas y el público, así como las plantillas inline.
bind
Dirección IP que utilizará el servidor integrado (por defecto: 0.0.0.0).
default_encoding
Encoding utilizado cuando el mismo se desconoce (por defecto "utf-8").
dump_errors
Mostrar errores en el log.
environment
Entorno actual, por defecto toma el valor de ENV['RACK_ENV'], o "development" si no está disponible.
logging
Define si se utiliza el logger.
lock
Coloca un lock alrededor de cada petición, procesando solamente una por proceso. Habilitá esta opción si tu aplicación no es thread-safe. Se encuentra deshabilitada por defecto.
method_override
Utiliza el parámetro _method para permtir formularios put/delete en navegadores que no los soportan.
port
Puerto en el que escuchará el servidor integrado.
prefixed_redirects
Define si inserta request.script_name en las redirecciones cuando no se proporciona un path absoluto. De esta manera, cuando está habilitada, redirect '/foo' se comporta de la misma manera que redirect to('/foo'). 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_dir
Alias para public_folder, que se encuentra a continuación.
public_folder
Path del directorio desde donde se sirven los archivos públicos. Solo se utiliza cuando se sirven archivos estáticos (ver la opción static). Si no está presente, se infiere del valor de la opción app_file.
reload_templates
Define si se recargan las plantillas entre peticiones. Se encuentra activado en el entorno de desarrollo.
root
Path del directorio raíz del proyecto. Si no está presente, se infiere del valor de la opción app_file.
raise_errors
Elevar excepciones (detiene la aplicación). Se encuentra activada por defecto cuando el valor de environment es "test". En caso contrario estará desactivada.
run
Cuando está habilitada, Sinatra se va a encargar de iniciar el servidor web, no la habilites cuando estés usando rackup o algún otro medio.
running
Indica si el servidor integrado está ejecutandose, ¡no cambiés esta configuración!.
server
Servidor, o lista de servidores, para usar como servidor integrado. Por defecto: ['thin', 'mongrel', 'webrick'], el orden establece la prioridad.
sessions
Habilita el soporte de sesiones basadas en cookies a través de Rack::Session::Cookie. Ver la sección 'Usando Sesiones' para más información.
show_exceptions
Muestra un stack trace en el navegador cuando ocurre una excepción. Se encuentra activada por defecto cuando el valor de environment es "development". En caso contrario estará desactivada.
static
Define si Sinatra debe encargarse de servir archivos estáticos. Deshabilitala cuando uses un servidor capaz de hacerlo por sí solo, porque mejorará el rendimiento. Se encuentra habilitada por defecto en el estilo clásico y desactivado en el el modular.
static_cache_control
Cuando Sinatra está sirviendo archivos estáticos, y está opción está habilitada, les va a agregar encabezados Cache-Control a las respuestas. Para esto utiliza el helper cache_control. Se encuentra deshabilitada por defecto. Notar que es necesario utilizar un array cuando se asignan múltiples valores: set :static_cache_control, [:public, :max_age => 300].
views
Path del directorio de las vistas. Si no está presente, se infiere del valor de la opción app_file.
## Entornos Existen tres entornos (`environments`) predefinidos: `development`, `production` y `test`. El entorno por defecto es `development` y tiene algunas particularidades: * Se recargan las plantillas entre una petición y la siguiente, a diferencia de `production` y `test`, donde se cachean. * Se instalan manejadores de errores `not_found` y `error` especiales que muestran un stack trace en el navegador cuando son disparados. Para utilizar alguno de los otros entornos puede asignarse el valor correspondiente a la variable de entorno `RACK_ENV`, o bien utilizar la opción `-e` al ejecutar la aplicación: ``` shell ruby mi_app.rb -e ``` Los métodos `development?`, `test?` y `production?` te permiten conocer el entorno actual. ## Manejo de Errores Los manejadores de errores se ejecutan dentro del mismo contexto que las rutas y los filtros `before`, lo que significa que podés usar, por ejemplo, `haml`, `erb`, `halt`, etc. ### No encontrado (Not Found) Cuando se eleva una excepción `Sinatra::NotFound`, o el código de estado de la respuesta es 404, el manejador `not_found` es invocado: ``` ruby not_found do 'No existo' end ``` ### Error El manejador `error` es invocado cada vez que una excepción es elevada desde un bloque de ruta o un filtro. El objeto de la excepción se puede obtener de la variable Rack `sinatra.error`: ``` ruby error do 'Disculpá, ocurrió un error horrible - ' + env['sinatra.error'].name end ``` Errores personalizados: ``` ruby error MiErrorPersonalizado do 'Lo que pasó fue...' + env['sinatra.error'].message end ``` Entonces, si pasa esto: ``` ruby get '/' do raise MiErrorPersonalizado, 'algo malo' end ``` Obtenés esto: Lo que pasó fue... algo malo También, podés instalar un manejador de errores para un código de estado: ``` ruby error 403 do 'Acceso prohibido' end get '/secreto' do 403 end ``` O un rango: ``` ruby error 400..510 do 'Boom' end ``` Sinatra instala manejadores `not_found` y `error` especiales cuando se ejecuta dentro del entorno de desarrollo "development". ## Rack Middleware Sinatra corre sobre Rack[http://rack.rubyforge.org/], una interfaz minimalista que es un estándar para frameworks webs escritos en Ruby. Una de las características más interesantes de Rack para los desarrolladores de aplicaciones es el soporte de "middleware" -- componentes que se ubican entre el servidor y tu aplicación, supervisando y/o manipulando la petición/respuesta HTTP para proporcionar varios tipos de funcionalidades comunes. Sinatra hace muy sencillo construir tuberías de Rack middleware a través del método top-level `use`: ``` ruby require 'sinatra' require 'mi_middleware_personalizado' use Rack::Lint use MiMiddlewarePersonalizado get '/hola' do 'Hola Mundo' end ``` La semántica de `use` es idéntica a la definida para el DSL Rack::Builder[http://rack.rubyforge.org/doc/classes/Rack/Builder.html] (más frecuentemente usado en archivos rackup). Por ejemplo, el método `use` acepta argumentos múltiples/variables así como bloques: ``` ruby use Rack::Auth::Basic do |nombre_de_usuario, password| nombre_de_usuario == 'admin' && password == 'secreto' end ``` Rack es distribuido con una variedad de middleware estándar para logging, debugging, enrutamiento URL, autenticación, y manejo de sesiones. Sinatra usa muchos de estos componentes automáticamente de acuerdo a su configuración para que típicamente no tengas que usarlas (con `use`) explícitamente. Podés encontrar middleware útil en [rack](https://github.com/rack/rack/tree/master/lib/rack), [rack-contrib](https://github.com/rack/rack-contrib#readme), con [CodeRack](http://coderack.org/) o en la [Rack wiki](https://github.com/rack/rack/wiki/List-of-Middleware). ## Pruebas Las pruebas para las aplicaciones Sinatra pueden ser escritas utilizando cualquier framework o librería de pruebas basada en Rack. Se recomienda usar [Rack::Test](http://rdoc.info/github/brynary/rack-test/master/frames): ``` ruby require 'mi_app_sinatra' require 'test/unit' require 'rack/test' class MiAppTest < Test::Unit::TestCase include Rack::Test::Methods def app Sinatra::Application end def test_mi_defecto get '/' assert_equal 'Hola Mundo!', last_response.body end def test_con_parametros get '/saludar', :name => 'Franco' assert_equal 'Hola Frank!', last_response.body end def test_con_entorno_rack get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' assert_equal "Estás usando Songbird!", last_response.body end end ``` ## Sinatra::Base - Middleware, Librerías, y Aplicaciones Modulares Definir tu aplicación en el top-level funciona bien para micro-aplicaciones pero trae inconvenientes considerables a la hora de construir componentes reutilizables como Rack middleware, Rails metal, librerías simples con un componente de servidor, o incluso extensiones de Sinatra. El DSL de top-level asume una configuración apropiada para micro-aplicaciones (por ejemplo, un único archivo de aplicación, los directorios `./public` y `./views`, logging, página con detalles de excepción, etc.). Ahí es donde `Sinatra::Base` entra en el juego: ``` ruby require 'sinatra/base' class MiApp < Sinatra::Base set :sessions, true set :foo, 'bar' get '/' do 'Hola Mundo!' end end ``` Las subclases de `Sinatra::Base` tienen disponibles exactamente los mismos métodos que los provistos por el DSL de top-level. La mayoría de las aplicaciones top-level se pueden convertir en componentes `Sinatra::Base` con dos modificaciones: * Tu archivo debe requerir `sinatra/base` en lugar de `sinatra`; de otra manera, todos los métodos del DSL de sinatra son importados dentro del espacio de nombres principal. * Poné las rutas, manejadores de errores, filtros y opciones de tu aplicación en una subclase de `Sinatra::Base`. `Sinatra::Base` es una pizarra en blanco. La mayoría de las opciones están desactivadas por defecto, incluyendo el servidor incorporado. Mirá [Opciones y Configuraciones](http://sinatra.github.com/configuration.html) para detalles sobre las opciones disponibles y su comportamiento. ### Estilo Modular vs. Clásico Contrariamente a la creencia popular, no hay nada de malo con el estilo clásico. Si se ajusta a tu aplicación, no es necesario que la cambies a una modular. Las desventaja de usar el estilo clásico en lugar del modular consiste en que solamente podés tener una aplicación Sinatra por proceso Ruby. Si tenés planificado usar más, cambiá al estilo modular. Al mismo tiempo, tené en cuenta que no hay ninguna razón por la cuál no puedas mezclar los estilos clásico y modular. A continuación se detallan las diferencias (sutiles) entre las configuraciones de ambos estilos:
Configuración Clásica Modular
app_file archivo que carga sinatra archivo con la subclase de Sinatra::Base
run $0 == app_file false
logging true false
method_override true false
inline_templates true false
static true false
### Sirviendo una Aplicación Modular Las dos opciones más comunes para iniciar una aplicación modular son, iniciarla activamente con `run!`: ``` ruby # mi_app.rb require 'sinatra/base' class MiApp < Sinatra::Base # ... código de la app ... # iniciar el servidor si el archivo fue ejecutado directamente run! if app_file == $0 end ``` Iniciar con: ``` shell ruby mi_app.rb ``` O, con un archivo `config.ru`, que permite usar cualquier handler Rack: ``` ruby # config.ru require './mi_app' run MiApp ``` Después ejecutar: ``` shell rackup -p 4567 ``` ### Usando una Aplicación Clásica con un Archivo config.ru Escribí el archivo de tu aplicación: ``` ruby # app.rb require 'sinatra' get '/' do 'Hola mundo!' end ``` Y el `config.ru` correspondiente: ``` ruby require './app' run Sinatra::Application ``` ### ¿Cuándo Usar config.ru? Indicadores de que probablemente querés usar `config.ru`: * Querés realizar el deploy con un hanlder Rack distinto (Passenger, Unicorn, Heroku, ...). * Querés usar más de una subclase de `Sinatra::Base`. * Querés usar Sinatra únicamente para middleware, pero no como un endpoint. No hay necesidad de utilizar un archivo `config.ru` exclusivamente porque tenés una aplicación modular, y no necesitás una aplicación modular para iniciarla con `config.ru`. ### Utilizando Sinatra como Middleware Sinatra no solo es capaz de usar otro Rack middleware, sino que a su vez, cualquier aplicación Sinatra puede ser agregada delante de un endpoint Rack como middleware. Este endpoint puede ser otra aplicación Sinatra, o cualquier aplicación basada en Rack (Rails/Ramaze/Camping/...): ``` ruby require 'sinatra/base' class PantallaDeLogin < Sinatra::Base enable :sessions get('/login') { haml :login } post('/login') do if params[:nombre] == 'admin' && params[:password] == 'admin' session['nombre_de_usuario'] = params[:nombre] else redirect '/login' end end end class MiApp < Sinatra::Base # el middleware se ejecutará antes que los filtros use PantallaDeLogin before do unless session['nombre_de_usuario'] halt "Acceso denegado, por favor iniciá sesión." end end get('/') { "Hola #{session['nombre_de_usuario']}." } end ``` ### Creación Dinámica de Aplicaciones Puede que en algunas ocasiones quieras crear nuevas aplicaciones en tiempo de ejecución sin tener que asignarlas a una constante. Para esto tenés `Sinatra.new`: ``` ruby require 'sinatra/base' mi_app = Sinatra.new { get('/') { "hola" } } mi_app.run! ``` Acepta como argumento opcional una aplicación desde la que se heredará: ``` ruby # config.ru require 'sinatra/base' controller = Sinatra.new do enable :logging helpers MisHelpers end map('/a') do run Sinatra.new(controller) { get('/') { 'a' } } end map('/b') do run Sinatra.new(controller) { get('/') { 'b' } } end ``` Construir aplicaciones de esta forma resulta especialmente útil para testear extensiones Sinatra o para usar Sinatra en tus librerías. Por otro lado, hace extremadamente sencillo usar Sinatra como middleware: ``` ruby require 'sinatra/base' use Sinatra do get('/') { ... } end run ProyectoRails::Application ``` ## Ámbitos y Ligaduras El ámbito en el que te encontrás determina que métodos y variables están disponibles. ### Ámbito de Aplicación/Clase Cada aplicación Sinatra es una subclase de `Sinatra::Base`. Si estás usando el DSL de top-level (`require 'sinatra'`), entonces esta clase es `Sinatra::Application`, de otra manera es la subclase que creaste explícitamente. Al nivel de la clase tenés métodos como `get` o `before`, pero no podés acceder a los objetos `request` o `session`, ya que hay una única clase de la aplicación para todas las peticiones. Las opciones creadas utilizando `set` son métodos al nivel de la clase: ``` ruby class MiApp < Sinatra::Base # Ey, estoy en el ámbito de la aplicación! set :foo, 42 foo # => 42 get '/foo' do # Hey, ya no estoy en el ámbito de la aplicación! end end ``` Tenés la ligadura al ámbito de la aplicación dentro de: * El cuerpo de la clase de tu aplicación * Métodos definidos por extensiones * El bloque pasado a `helpers` * Procs/bloques usados como el valor para `set` Este ámbito puede alcanzarse de las siguientes maneras: * A través del objeto pasado a los bloques de configuración (`configure { |c| ...}`) * Llamando a `settings` desde dentro del ámbito de la petición ### Ámbito de Petición/Instancia Para cada petición entrante, una nueva instancia de la clase de tu aplicación es creada y todos los bloques de rutas son ejecutados en ese ámbito. Desde este ámbito podés acceder a los objetos `request` y `session` o llamar a los métodos de renderización como `erb` o `haml`. Podés acceder al ámbito de la aplicación desde el ámbito de la petición utilizando `settings`: ``` ruby class MiApp < Sinatra::Base # Ey, estoy en el ámbito de la aplicación! get '/definir_ruta/:nombre' do # Ámbito de petición para '/definir_ruta/:nombre' @valor = 42 settings.get("/#{params[:nombre]}") do # Ámbito de petición para "/#{params[:nombre]}" @valor # => nil (no es la misma petición) end "Ruta definida!" end end ``` Tenés la ligadura al ámbito de la petición dentro de: * bloques pasados a get/head/post/put/delete/options * filtros before/after * métodos ayudantes * plantillas/vistas ### Ámbito de Delegación El ámbito de delegación solo reenvía métodos al ámbito de clase. De cualquier manera, no se comporta 100% como el ámbito de clase porque no tenés la ligadura de la clase: únicamente métodos marcados explícitamente para delegación están disponibles y no compartís variables/estado con el ámbito de clase (léase: tenés un `self` diferente). Podés agregar delegaciones de método llamando a `Sinatra::Delegator.delegate :nombre_del_metodo`. Tenés la ligadura al ámbito de delegación dentro de: * La ligadura del top-level, si hiciste `require "sinatra"` * Un objeto extendido con el mixin `Sinatra::Delegator` Hechale un vistazo al código: acá está el [Sinatra::Delegator mixin](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/base.rb#L1609-1633) que [extiende el objeto main](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/main.rb#L28-30). ## Línea de Comandos Las aplicaciones Sinatra pueden ser ejecutadas directamente: ``` shell ruby miapp.rb [-h] [-x] [-e ENTORNO] [-p PUERTO] [-o HOST] [-s MANEJADOR] ``` Las opciones son: ``` -h # ayuda -p # asigna el puerto (4567 es usado por defecto) -o # asigna el host (0.0.0.0 es usado por defecto) -e # asigna el entorno (development es usado por defecto) -s # especifica el servidor/manejador rack (thin es usado por defecto) -x # activa el mutex lock (está desactivado por defecto) ``` ## Versiones de Ruby Soportadas 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 prohiba, te recomendamos que uses 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. Hemos dejado de soportar Ruby 1.8.6. 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. No uses 1.9.2p0, porque se producen fallos de segmentación cuando se ejecuta Sinatra. El soporte se mantendrá al menos hasta que se libere la versión 1.9.4/2.0 de Ruby. El soporte para la última versión de la serie 1.9 se mantendrá mientras lo haga el core team de Ruby.
Ruby 1.9.3
1.9.3 es soportado y recomendado. Tené en cuenta que el cambio a 1.9.3 desde una versión anterior va a invalidar todas las sesiones.
Rubinius
Rubinius es soportado oficialmente (Rubinius >= 1.2.4). Todo funciona correctamente, incluyendo los lenguajes de plantillas. La próxima versión, 2.0, también es soportada, incluyendo el modo 1.9.
JRuby
JRuby es soportado oficialmente (JRuby >= 1.6.7). 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, Redcarpet, RedCloth y Yajl, así como Thin y Mongrel se ven afectadas.
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 (pero no te recomendamos que los uses) 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 2.1.0) y la rama 1.9.4. Como están en movimiento constante, no podemos garantizar nada. De todas formas, podés contar con que tanto 1.9.4-p0 como 2.1.0-p0 sea soportadas. 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 tu aplicación sobre la rama master, en general es bastante estable. También liberamos prereleases de vez en cuando, así, podés hacer: ``` shell gem install sinatra --pre ``` Para obtener algunas de las últimas características. ### Con Bundler Esta es la manera recomendada para ejecutar tu aplicación sobre la última versión de Sinatra usando [Bundler](http://gembundler.com/). Primero, instalá bundler si no lo hiciste todavía: ``` shell gem install bundler ``` Después, en el directorio de tu proyecto, creá un archivo `Gemfile`: ``` ruby source :rubygems gem 'sinatra', :git => "git://github.com/sinatra/sinatra.git" # otras dependencias gem 'haml' # por ejemplo, si usás haml gem 'activerecord', '~> 3.0' # quizás también necesités ActiveRecord 3.x ``` Tené en cuenta que tenés que listar todas las dependencias directas de tu aplicación. No es necesario listar las dependencias de Sinatra (Rack y Tilt) porque Bundler las agrega directamente. Ahora podés arrancar tu aplicación así: ``` shell bundle exec ruby miapp.rb ``` ### Con Git Cloná el repositorio localmente y ejecutá tu aplicación, asegurándote que el directorio `sinatra/lib` esté en el `$LOAD_PATH`: ``` shell cd miapp git clone git://github.com/sinatra/sinatra.git ruby -Isinatra/lib miapp.rb ``` Para actualizar el código fuente de Sinatra en el futuro: ``` shell cd miapp/sinatra git pull ``` ### Instalación Global Podés construir la gem vos mismo: ``` shell git clone git://github.com/sinatra/sinatra.git cd sinatra rake sinatra.gemspec rake install ``` Si instalás tus gems como root, el último paso debería ser ``` shell sudo rake install ``` ## Versionado Sinatra utiliza el [Versionado Semántico](http://semver.org/), siguiendo las especificaciones SemVer y SemVerTag. ## Lecturas Recomendadas * [Sito web del proyecto](http://www.sinatrarb.com/) - Documentación adicional, noticias, y enlaces a otros recursos. * [Contribuyendo](http://www.sinatrarb.com/contributing) - ¿Encontraste un error?. ¿Necesitás ayuda?. ¿Tenés un parche?. * [Seguimiento de problemas](http://github.com/sinatra/sinatra/issues) * [Twitter](http://twitter.com/sinatra) * [Lista de Correo](http://groups.google.com/group/sinatrarb/topics) * [IRC: #sinatra](irc://chat.freenode.net/#sinatra) en http://freenode.net * [Sinatra Book](http://sinatra-book.gittr.com) Tutorial (en inglés). * [Sinatra Recipes](http://recipes.sinatrarb.com/) Recetas contribuidas por la comunidad (en inglés). * Documentación de la API para la [ú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 CI](http://travis-ci.org/sinatra/sinatra) sinatra-1.4.3/README.de.md0000644000004100000410000022332712161612727015074 0ustar www-datawww-data# Sinatra *Wichtig: Dieses Dokument ist eine Übersetzung aus dem Englischen und unter Umständen nicht auf dem aktuellen Stand (aktuell Sinatra 1.4.2).* 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: Sinatra via `rubygems` installieren: ```shell gem install sinatra ``` Eine Datei mit dem Namen `myapp.rb` erstellen: ```ruby require 'sinatra' get '/' do 'Hallo Welt!' end ``` und im gleichen Verzeichnis ausführen: ```shell ruby myapp.rb ``` Die Seite kann nun unter [http://localhost:4567](http://localhost:4567) aufgerufen werden. ## Inhalt * [Sinatra](#sinatra) * [Routen](#routen) * [Bedingungen](#bedingungen) * [Rückgabewerte](#rckgabewerte) * [Eigene Routen-Muster](#eigene-routen-muster) * [Statische Dateien](#statische-dateien) * [Views/Templates](#viewstemplates) * [Direkte Templates](#direkte-templates) * [Verfügbare Templatesprachen](#verfgbare-templatesprachen) * [Haml Templates](#haml-templates) * [Erb Templates](#erb-templates) * [Builder Templates](#builder-templates) * [Nokogiri Templates](#nokogiri-templates) * [Sass Templates](#sass-templates) * [SCSS Templates](#scss-templates) * [Less Templates](#less-templates) * [Liquid Templates](#liquid-templates) * [Markdown Templates](#markdown-templates) * [Textile Templates](#textile-templates) * [RDoc Templates](#rdoc-templates) * [Radius Templates](#radius-templates) * [Markaby Templates](#markaby-templates) * [RABL Templates](#rabl-templates) * [Slim Templates](#slim-templates) * [Creole Templates](#creole-templates) * [CoffeeScript Templates](#coffeescript-templates) * [Stylus Templates](#stylus-templates) * [Yajl Templates](#yajl-templates) * [WLang Templates](#wlang-templates) * [Auf Variablen in Templates zugreifen](#auf-variablen-in-templates-zugreifen) * [Templates mit `yield` und verschachtelte Layouts](#templates-mit-yield-und-verschachtelte-layouts) * [Inline-Templates](#inline-templates) * [Benannte Templates](#benannte-templates) * [Dateiendungen zuordnen](#dateiendungen-zuordnen) * [Eine eigene Template-Engine hinzufügen](#eine-eigene-template-engine-hinzufgen) * [Filter](#filter) * [Helfer](#helfer) * [Sessions verwenden](#sessions-verwenden) * [Anhalten](#anhalten) * [Weiterspringen](#weiterspringen) * [Eine andere Route ansteuern](#eine-andere-route-ansteuern) * [Body, Status-Code und Header setzen](#body-status-code-und-header-setzen) * [Response-Streams](#response-streams) * [Logger](#logger) * [Mime-Types](#mime-types) * [URLs generieren](#urls-generieren) * [Browser-Umleitung](#browser-umleitung) * [Cache einsetzen](#cache-einsetzen) * [Dateien versenden](#dateien-versenden) * [Das Request-Objekt](#das-request-objekt) * [Anhänge](#anhnge) * [Umgang mit Datum und Zeit](#umgang-mit-datum-und-zeit) * [Nachschlagen von Template-Dateien](#nachschlagen-von-template-dateien) * [Konfiguration](#konfiguration) * [Einstellung des Angriffsschutzes](#einstellung-des-angriffsschutzes) * [Mögliche Einstellungen](#mgliche-einstellungen) * [Umgebungen](#umgebungen) * [Fehlerbehandlung](#fehlerbehandlung) * [Nicht gefunden](#nicht-gefunden) * [Fehler](#fehler) * [Rack-Middleware](#rack-middleware) * [Testen](#testen) * [Sinatra::Base - Middleware, Bibliotheken und modulare Anwendungen](#sinatrabase---middleware-bibliotheken-und-modulare-anwendungen) * [Modularer vs. klassischer Stil](#modularer-vs-klassischer-stil) * [Eine modulare Applikation bereitstellen](#eine-modulare-applikation-bereitstellen) * [Eine klassische Anwendung mit einer config.ru verwenden](#eine-klassische-anwendung-mit-einer-configru-verwenden) * [Wann sollte eine config.ru-Datei verwendet werden?](#wann-sollte-eine-configru-datei-verwendet-werden) * [Sinatra als Middleware nutzen](#sinatra-als-middleware-nutzen) * [Dynamische Applikationserstellung](#dynamische-applikationserstellung) * [Geltungsbereich und Bindung](#geltungsbereich-und-bindung) * [Anwendungs- oder Klassen-Scope](#anwendungs--oder-klassen-scope) * [Anfrage- oder Instanz-Scope](#anfrage--oder-instanz-scope) * [Delegation-Scope](#delegation-scope) * [Kommandozeile](#kommandozeile) * [Systemanforderungen](#systemanforderungen) * [Der neuste Stand (The Bleeding Edge)](#der-neuste-stand-the-bleeding-edge) * [Mit Bundler](#mit-bundler) * [Eigenes Repository](#eigenes-repository) * [Gem erstellen](#gem-erstellen) * [Versions-Verfahren](#versions-verfahren) * [Mehr](#mehr) ## Routen In Sinatra wird eine Route durch eine HTTP-Methode und ein URL-Muster definiert. Jeder dieser Routen wird ein Ruby-Block zugeordnet: ```ruby get '/' do .. zeige etwas .. end post '/' do .. erstelle etwas .. end put '/' do .. update etwas .. end delete '/' do .. entferne etwas .. end options '/' do .. zeige, was wir können .. end link '/' do .. verbinde etwas .. end unlink '/' do .. trenne etwas .. end ``` Die Routen werden in der Reihenfolge durchlaufen, in der sie definiert wurden. Das erste Routen-Muster, das mit dem Request übereinstimmt, wird ausgeführt. Die Muster der Routen können benannte Parameter beinhalten, die über den `params`-Hash zugänglich gemacht werden: ```ruby get '/hallo/:name' do # passt auf "GET /hallo/foo" und "GET /hallo/bar" # params[:name] ist dann 'foo' oder 'bar' "Hallo #{params[:name]}!" end ``` Man kann auf diese auch mit Block-Parametern zugreifen: ```ruby get '/hallo/:name' do |n| # n entspricht hier params[:name] "Hallo #{n}!" end ``` Routen-Muster können auch mit sog. Splat- oder Wildcard-Parametern über das `params[:splat]`-Array angesprochen werden: ```ruby get '/sag/*/zu/*' do # passt z.B. auf /sag/hallo/zu/welt params[:splat] # => ["hallo", "welt"] end get '/download/*.*' do # passt auf /download/pfad/zu/datei.xml params[:splat] # => ["pfad/zu/datei", "xml"] end ``` Oder mit Block-Parametern: ```ruby get '/download/*.*' do |pfad, endung| [pfad, endung] # => ["Pfad/zu/Datei", "xml"] end ``` Routen mit regulären Ausdrücken sind auch möglich: ```ruby get %r{/hallo/([\w]+)} do "Hallo, #{params[:captures].first}!" end ``` Und auch hier können Block-Parameter genutzt werden: ```ruby get %r{/hallo/([\w]+)} do |c| "Hallo, #{c}!" end ``` Routen-Muster können auch mit optionalen Parametern ausgestattet werden: ```ruby get '/posts.?:format?' do # passt auf "GET /posts" sowie jegliche Erweiterung # wie "GET /posts.json", "GET /posts.xml" etc. end ``` Anmerkung: Solange man den sog. Path Traversal Attack-Schutz nicht deaktiviert (siehe weiter unten), kann es sein, dass der Request-Pfad noch vor dem Abgleich mit den Routen modifiziert wird. ### Bedingungen An Routen können eine Vielzahl von Bedingungen geknüpft werden, die erfüllt sein müssen, damit der Block ausgeführt wird. Möglich wäre etwa eine Einschränkung des User-Agents über die interne Bedingung `:agent`: ```ruby get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do "Du verwendest Songbird Version #{params[:agent][0]}" end ``` Wird Songbird als Browser nicht verwendet, springt Sinatra zur nächsten Route: ```ruby get '/foo' do # passt auf andere Browser end ``` Andere mitgelieferte Bedingungen sind `:host_name` und `:provides`: ```ruby get '/', :host_name => /^admin\./ do "Adminbereich, Zugriff verweigert!" end get '/', :provides => 'html' do haml :index end get '/', :provides => ['rss', 'atom', 'xml'] do builder :feed end ``` Eigene Bedingungen können relativ einfach hinzugefügt werden: ```ruby set(:wahrscheinlichkeit) { |value| condition { rand <= value } } get '/auto_gewinnen', :wahrscheinlichkeit => 0.1 do "Du hast gewonnen!" end get '/auto_gewinnen' do "Tut mir leid, verloren." end ``` Bei Bedingungen, die mehrere Werte annehmen können, sollte ein Splat verwendet werden: ```ruby set(:auth) do |*roles| # <- hier kommt der Splat ins Spiel condition do unless logged_in? && roles.any? {|role| current_user.in_role? role } redirect "/login/", 303 end end end get "/mein/account/", :auth => [:user, :admin] do "Mein Account" end get "/nur/admin/", :auth => :admin do "Nur Admins dürfen hier rein!" end ``` ### Rückgabewerte Durch den Rückgabewert eines Routen-Blocks wird mindestens der Response-Body festgelegt, der an den HTTP-Client, bzw. die nächste Rack-Middleware, weitergegeben wird. Im Normalfall handelt es sich hierbei, wie in den vorangehenden Beispielen zu sehen war, um einen String. Es werden allerdings auch andere Werte akzeptiert. Es kann jedes gültige Objekt zurückgegeben werden, bei dem es sich entweder um einen Rack-Rückgabewert, einen Rack-Body oder einen HTTP-Status-Code handelt: * Ein Array mit drei Elementen: `[Status (Fixnum), Headers (Hash), Response-Body (antwortet auf #each)]`. * Ein Array mit zwei Elementen: `[Status (Fixnum), Response-Body (antwortet auf #each)]`. * Ein Objekt, das auf `#each` antwortet und den an diese Methode übergebenen Block nur mit Strings als Übergabewerte aufruft. * Ein Fixnum, das den Status-Code festlegt. Damit lässt sich relativ einfach Streaming implementieren: ```ruby class Stream def each 100.times { |i| yield "#{i}\n" } end end get('/') { Stream.new } ``` Ebenso kann die `stream`-Helfer-Methode (s.u.) verwendet werden, die Streaming direkt in die Route integriert. ### Eigene Routen-Muster Wie oben schon beschrieben, ist Sinatra von Haus aus mit Unterstützung für String-Muster und Reguläre Ausdrücke zum Abgleichen von Routen ausgestattet. Das muss aber noch nicht alles sein, es können ohne großen Aufwand eigene Routen-Muster erstellt werden: ```ruby class AllButPattern Match = Struct.new(:captures) def initialize(except) @except = except @captures = Match.new([]) end def match(str) @captures unless @except === str end end def all_but(pattern) AllButPattern.new(pattern) end get all_but("/index") do # ... end ``` Beachte, dass das obige Beispiel etwas übertrieben wirkt. Es geht auch einfacher: ```ruby get // do pass if request.path_info == "/index" # ... end ``` Oder unter Verwendung eines negativen look ahead: ```ruby get %r{^(?!/index$)} do # ... end ``` ## Statische Dateien Statische Dateien werden im `./public`-Ordner erwartet. Es ist möglich, einen anderen Ort zu definieren, indem man die `:public_folder`-Option setzt: ```ruby set :public_folder, File.dirname(__FILE__) + '/static' ``` Zu beachten ist, dass der Ordnername `public` nicht Teil der URL ist. Die Datei `./public/css/style.css` ist unter `http://example.com/css/style.css` zu finden. Um den `Cache-Control`-Header mit Informationen zu versorgen, verwendet man die `:static_cache_control`-Einstellung (s.u.). ## Views/Templates Alle Templatesprachen verwenden ihre eigene Renderingmethode, die jeweils einen String zurückgibt: ```ruby get '/' do erb :index end ``` Dieses Beispiel rendert `views/index.erb`. Anstelle eines Templatenamens kann man auch direkt die Templatesprache verwenden: ```ruby get '/' do code = "<%= Time.now %>" erb code end ``` Templates nehmen ein zweite Argument an, den Options-Hash: ```ruby get '/' do erb :index, :layout => :post end ``` Dieses Beispiel rendert `views/index.erb` eingebettet in `views/post.erb` (Voreinstellung ist `views/layout.erb`, sofern es vorhanden ist.) Optionen, die Sinatra nicht versteht, werden an das Template weitergereicht: ```ruby get '/' do haml :index, :format => :html5 end ``` Für alle Templates können auch Einstellungen, die für alle Routen gelten, festgelegt werden: ```ruby set :haml, :format => :html5 get '/' do haml :index end ``` Optionen, die an die Rendermethode weitergegeben werden, überschreiben die Einstellungen, die mit `set` festgelegt wurden. Einstellungen:
locals
Liste von lokalen Variablen, die an das Dokument weitergegeben werden. Praktisch für Partials: 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 geladen werden. Voreingestellt auf settings.views.
layout
Legt fest, ob ein Layouttemplate verwendet werden soll oder nicht (true oderfalse). Ist es ein Symbol, dann legt es fest, welches Template als Layout verwendet wird: erb :index, :layout => !request.xhr?
content_type
Content-Typ 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: set :rdoc, :layout_engine => :erb
layout_options
Besondere Einstellungen, die nur für das Rendering verwendet werden: set :rdoc, :layout_options => { :views => 'views/layouts' }
Sinatra geht davon aus, dass die Templates sich im `./views` Verzeichnis befinden. Es kann jedoch ein anderer Ordner festgelegt werden: ```ruby set :views, settings.root + '/templates' ``` Es ist zu beachten, dass immer mit Symbolen auf Templates verwiesen werden muss, auch dann, wenn sie sich in einem Unterordner befinden: ```ruby haml :'unterverzeichnis/template' ``` Rendering-Methoden rendern jeden String direkt. ### Direkte Templates ``` ruby get '/' do haml '%div.title Hallo Welt' end ``` Hier wird der String direkt gerendert. ### Verfügbare Templatesprachen Einige Sprachen haben mehrere Implementierungen. Um festzulegen, welche verwendet wird (und dann auch Thread-sicher ist), verwendet man am besten zu Beginn ein `'require'`: ```ruby require 'rdiscount' # oder require 'bluecloth' get('/') { markdown :index } ``` #### Haml Templates
Abhängigkeit haml
Dateierweiterung .haml
Beispiel haml :index, :format => :html5
#### Erb Templates
Abhängigkeit erubis oder erb (Standardbibliothek von Ruby)
Dateierweiterungen .erb, .rhtml oder .erubis (nur Erubis)
Beispiel erb :index
#### Builder Templates
Abhängigkeit builder
Dateierweiterung .builder
Beispiel builder { |xml| xml.em "Hallo" }
Nimmt ebenso einen Block für Inline-Templates entgegen (siehe Beispiel). #### Nokogiri Templates
Abhängigkeit nokogiri
Dateierweiterung .nokogiri
Beispiel nokogiri { |xml| xml.em "Hallo" }
Nimmt ebenso einen Block für Inline-Templates entgegen (siehe Beispiel). #### Sass Templates
Abhängigkeit sass
Dateierweiterung .sass
Beispiel sass :stylesheet, :style => :expanded
#### SCSS Templates
Abhängigkeit sass
Dateierweiterung .scss
Beispiel scss :stylesheet, :style => :expanded
#### Less Templates
Abhängigkeit less
Dateierweiterung .less
Beispiel less :stylesheet
#### Liquid Templates
Abhängigkeit liquid
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 Eine der folgenden Bibliotheken: RDiscount, RedCarpet, BlueCloth, kramdown oder maruku
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: ```ruby erb :overview, :locals => { :text => markdown(:einfuehrung) } ``` Beachte, dass man die `markdown`-Methode auch aus anderen Templates heraus aufrufen kann: ```ruby %h1 Gruß von Haml! %p= markdown(:Grüße) ``` Da man Ruby nicht von Markdown heraus aufrufen kann, können auch Layouts nicht in Markdown geschrieben werden. Es ist aber möglich, einen Renderer für die Templates zu verwenden und einen anderen für das Layout, indem die `:layout_engine`-Option verwendet wird. #### Textile Templates
Abhängigkeit RedCloth
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: ```ruby erb :overview, :locals => { :text => textile(:einfuehrung) } ``` Beachte, dass man die `textile`-Methode auch aus anderen Templates heraus aufrufen kann: ```ruby %h1 Gruß von Haml! %p= textile(:Grüße) ``` Da man Ruby nicht von Textile heraus aufrufen kann, können auch Layouts nicht in Textile geschrieben werden. Es ist aber möglich, einen Renderer für die Templates zu verwenden und einen anderen für das Layout, indem die `:layout_engine`-Option verwendet wird. #### RDoc Templates
Abhängigkeit rdoc
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: ```ruby erb :overview, :locals => { :text => rdoc(:einfuehrung) } ``` Beachte, dass man die `rdoc`-Methode auch aus anderen Templates heraus aufrufen kann: ```ruby %h1 Gruß von Haml! %p= rdoc(:Grüße) ``` Da man Ruby nicht von RDoc heraus aufrufen kann, können auch Layouts nicht in RDoc geschrieben werden. Es ist aber möglich, einen Renderer für die Templates zu verwenden und einen anderen für das Layout, indem die `:layout_engine`-Option verwendet wird. #### Radius Templates
Abhängigkeit radius
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
Dateierweiterung .mab
Beispiel markaby { h1 "Willkommen!" }
Nimmt ebenso einen Block für Inline-Templates entgegen (siehe Beispiel). #### RABL Templates
Abhängigkeit rabl
Dateierweiterung .rabl
Beispiel rabl :index
#### Slim Templates
Abhängigkeit slim
Dateierweiterung .slim
Beispiel slim :index
#### Creole Templates
Abhängigkeit 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: ```ruby erb :overview, :locals => { :text => creole(:einfuehrung) } ``` Beachte, dass man die `creole`-Methode auch aus anderen Templates heraus aufrufen kann: ```ruby %h1 Gruß von Haml! %p= creole(:Grüße) ``` Da man Ruby nicht von Creole heraus aufrufen kann, können auch Layouts nicht in Creole geschrieben werden. Es ist aber möglich, einen Renderer für die Templates zu verwenden und einen anderen für das Layout, indem die `:layout_engine`-Option verwendet wird. #### CoffeeScript Templates
Abhängigkeit coffee-script und eine Möglichkeit JavaScript auszuführen.
Dateierweiterung .coffee
Beispiel coffee :index
#### Stylus Templates
Abhängigkeit Stylus und eine Möglichkeit JavaScript auszuführen .
Dateierweiterung .styl
Beispiel stylus :index
Um Stylus-Templates ausführen zu können, müssen `stylus` und `stylus/tilt` zuerst geladen werden: ``` ruby require 'sinatra' require 'stylus' require 'stylus/tilt' get '/' do stylus :example end ``` #### Yajl Templates
Abhängigkeit yajl-ruby
Dateierweiterung .yajl
Beispiel yajl :index, :locals => { :key => 'qux' }, :callback => 'present', :variable => 'resource'
Die Template-Quelle wird als Ruby-String evaluiert. Die daraus resultierende json Variable wird mit Hilfe von `#to_json` umgewandelt: ``` ruby json = { :foo => 'bar' } json[:baz] = key ``` Die `:callback` und `:variable` Optionen können mit dem gerenderten Objekt verwendet werden: ``` ruby var resource = {"foo":"bar","baz":"qux"}; present(resource); ``` #### WLang Templates
Abhängigkeit 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. Rendert den eingebetteten Template-String. ### Auf Variablen in Templates zugreifen Templates werden in demselben Kontext ausgeführt wie Routen. Instanzvariablen in Routen sind auch direkt im Template verfügbar: ```ruby get '/:id' do @foo = Foo.find(params[:id]) haml '%h1= @foo.name' end ``` Oder durch einen expliziten Hash von lokalen Variablen: ```ruby get '/:id' do foo = Foo.find(params[:id]) haml '%h1= bar.name', :locals => { :bar => foo } end ``` Dies wird typischerweise bei Verwendung von Subtemplates (partials) in anderen Templates eingesetzt. ### Templates mit `yield` und verschachtelte Layouts Ein Layout ist üblicherweise ein Template, das ein `yield` aufruft. Ein solches Template kann entweder wie oben beschrieben über die `:template` Option verwendet werden oder mit einem Block gerendert werden: ``` ruby erb :post, :layout => false do erb :index end ``` Dieser Code entspricht weitestgehend `erb :index, :layout => :post`. Blöcke an Render-Methoden weiterzugeben ist besonders bei verschachtelten Layouts hilfreich: ``` ruby erb :main_layout, :layout => false do erb :admin_layout do erb :user end end ``` Der gleiche Effekt kann auch mit weniger Code erreicht werden: ``` ruby erb :admin_layout, :layout => :main_layout do erb :user end ``` Zur Zeit nehmen folgende Renderer Blöcke an: `erb`, `haml`, `liquid`, `slim ` und `wlang`. Das gleich gilt auch für die allgemeine `render` Methode. ### Inline-Templates Templates können auch am Ende der Datei definiert werden: ```ruby require 'sinatra' get '/' do haml :index end __END__ @@ layout %html = yield @@ index %div.title Hallo Welt!!!!! ``` Anmerkung: Inline-Templates, die in der Datei definiert sind, die `require 'sinatra'` aufruft, werden automatisch geladen. Um andere Inline-Templates in anderen Dateien aufzurufen, muss explizit `enable :inline_templates` verwendet werden. ### Benannte Templates Templates können auch mit der Top-Level `template`-Methode definiert werden: ```ruby template :layout do "%html\n =yield\n" end template :index do '%div.title Hallo Welt!' end get '/' do haml :index end ``` Wenn ein Template mit dem Namen "layout" existiert, wird es bei jedem Aufruf verwendet. Durch `:layout => false` kann das Ausführen verhindert werden: ```ruby get '/' do haml :index, :layout => !request.xhr? # !request.xhr? prüft, ob es sich um einen asynchronen Request handelt. # wenn nicht, dann verwende ein Layout (negiert durch !) end ``` ### Dateiendungen zuordnen Um eine Dateiendung einer Template-Engine zuzuordnen, kann `Tilt.register` genutzt werden. Wenn etwa die Dateiendung `tt` für Textile-Templates genutzt werden soll, lässt sich dies wie folgt bewerkstelligen: ```ruby Tilt.register :tt, Tilt[:textile] ``` ### Eine eigene Template-Engine hinzufügen Zu allererst muss die Engine bei Tilt registriert und danach eine Rendering-Methode erstellt werden: ```ruby Tilt.register :mtt, MeineTolleTemplateEngine helpers do def mtt(*args) render(:mtt, *args) end end get '/' do mtt :index end ``` Dieser Code rendert `./views/application.mtt`. Siehe [github.com/rtomayko/tilt](https://github.com/rtomayko/tilt), um mehr über Tilt zu erfahren. ## Filter Before-Filter werden vor jedem Request in demselben Kontext, wie danach die Routen, ausgeführt. So können etwa Request und Antwort geändert werden. Gesetzte Instanzvariablen in Filtern können in Routen und Templates verwendet werden: ```ruby before do @note = 'Hi!' request.path_info = '/foo/bar/baz' end get '/foo/*' do @note #=> 'Hi!' params[:splat] #=> 'bar/baz' end ``` After-Filter werden nach jedem Request in demselben Kontext ausgeführt und können ebenfalls Request und Antwort ändern. In Before-Filtern gesetzte Instanzvariablen können in After-Filtern verwendet werden: ```ruby after do puts response.status end ``` Filter können optional auch mit einem Muster ausgestattet werden, das auf den Request-Pfad passen muss, damit der Filter ausgeführt wird: ```ruby before '/protected/*' do authenticate! end after '/create/:slug' do |slug| session[:last_slug] = slug end ``` Ähnlich wie Routen können Filter auch mit weiteren Bedingungen eingeschränkt werden: ```ruby before :agent => /Songbird/ do # ... end after '/blog/*', :host_name => 'example.com' do # ... end ``` ## Helfer Durch die Top-Level `helpers`-Methode werden sogenannte Helfer-Methoden definiert, die in Routen und Templates verwendet werden können: ```ruby helpers do def bar(name) "#{name}bar" end end get '/:name' do bar(params[:name]) end ``` ### Sessions verwenden Sessions werden verwendet, um Zustände zwischen den Requests zu speichern. Sind sie aktiviert, kann ein Session-Hash je Benutzer-Session verwendet werden: ```ruby enable :sessions get '/' do "value = " << session[:value].inspect end get '/:value' do session[:value] = params[:value] end ``` Beachte, dass `enable :sessions` alle Daten in einem Cookie speichert. Unter Umständen kann dies negative Effekte haben, z.B. verursachen viele Daten höheren, teilweise überflüssigen Traffic. Um das zu vermeiden, kann eine Rack- Session-Middleware verwendet werden. Dabei wird auf `enable :sessions` verzichtet und die Middleware wie üblich im Programm eingebunden: ```ruby use Rack::Session::Pool, :expire_after => 2592000 get '/' do "value = " << session[:value].inspect end get '/:value' do session[:value] = params[:value] end ``` Um die Sicherheit zu erhöhen, werden Cookies, die Session-Daten führen, mit einem sogenannten Session-Secret signiert. Da sich dieses Geheimwort bei jedem Neustart der Applikation automatisch ändert, ist es sinnvoll, ein eigenes zu wählen, damit sich alle Instanzen der Applikation dasselbe Session-Secret teilen: ```ruby set :session_secret, 'super_geheimes_Gegeimnis' ``` Zur weiteren Konfiguration kann man einen Hash mit Optionen in den `sessions` Einstellungen ablegen. ```ruby set :sessions, :domain => 'foo.com' ``` ### Anhalten Zum sofortigen Stoppen eines Request in einem Filter oder einer Route: ```ruby halt ``` Der Status kann beim Stoppen mit angegeben werden: ```ruby halt 410 ``` Oder auch den Response-Body: ```ruby halt 'Hier steht der Body' ``` Oder beides: ```ruby halt 401, 'verschwinde!' ``` Sogar mit Headern: ```ruby halt 402, {'Content-Type' => 'text/plain'}, 'Rache' ``` Natürlich ist es auch möglich, ein Template mit `halt` zu verwenden: ```ruby halt erb(:error) ``` ### Weiterspringen Eine Route kann mittels `pass` zu der nächsten passenden Route springen: ```ruby get '/raten/:wer' do pass unless params[:wer] == 'Frank' 'Du hast mich!' end get '/raten/*' do 'Du hast mich nicht!' end ``` Der Block wird sofort verlassen und es wird nach der nächsten treffenden Route gesucht. Ein 404-Fehler wird zurückgegeben, wenn kein treffendes Routen-Muster gefunden wird. ### Eine andere Route ansteuern Wenn nicht zu einer anderen Route gesprungen werden soll, sondern nur das Ergebnis einer anderen Route gefordert wird, kann `call` für einen internen Request verwendet werden: ```ruby get '/foo' do status, headers, body = call env.merge("PATH_INFO" => '/bar') [status, headers, body.map(&:upcase)] end get '/bar' do "bar" end ``` Beachte, dass in dem oben angegeben Beispiel die Performance erheblich erhöht werden kann, wenn `"bar"` in eine Helfer-Methode umgewandelt wird, auf die `/foo` und `/bar` zugreifen können. Wenn der Request innerhalb derselben Applikations-Instanz aufgerufen und keine Kopie der Instanz erzeugt werden soll, kann `call!` anstelle von `call` verwendet werden. ### 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 anderer Stelle während der Ausführung gesetzt werden soll. Dafür kann man die Helfer-Methode `body` einsetzen. Ist sie gesetzt, kann sie zu einem späteren Zeitpunkt aufgerufen werden: ```ruby get '/foo' do body "bar" end after do puts body end ``` Ebenso ist es möglich, einen Block an `body` weiterzureichen, der dann vom Rack-Handler ausgeführt wird (lässt sich z.B. zur Umsetzung von Streaming einsetzen, siehe auch "Rückgabewerte"). Vergleichbar mit `body` lassen sich auch Status-Code und Header setzen: ```ruby get '/foo' do status 418 headers \ "Allow" => "BREW, POST, GET, PROPFIND, WHEN", "Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt" halt "Ich bin ein Teekesselchen" end ``` Genau wie bei `body` liest ein Aufrufen von `headers` oder `status` ohne Argumente den aktuellen Wert aus. ### Response-Streams In manchen Situationen sollen Daten bereits an den Client zurückgeschickt werden, bevor ein vollständiger Response bereit steht. Manchmal will man die Verbindung auch erst dann beenden und Daten so lange an den Client zurückschicken, bis er die Verbindung abbricht. Für diese Fälle gibt es die `stream`-Helfer-Methode, die es einem erspart eigene Lösungen zu schreiben: ```ruby get '/' do stream do |out| out << "Das ist ja mal wieder fanta -\n" sleep 0.5 out << " (bitte warten…) \n" sleep 1 out << "- stisch!\n" end end ``` Damit lassen sich Streaming-APIs realisieren, sog. [Server Sent Events](http://dev.w3.org/html5/eventsource/), die als Basis für [WebSockets](http://en.wikipedia.org/wiki/WebSocket) dienen. Ebenso können sie verwendet werden, um den Durchsatz zu erhöhen, wenn ein Teil der Daten von langsamen Ressourcen abhängig ist. Es ist zu beachten, dass das Verhalten beim Streaming, insbesondere die Anzahl nebenläufiger Anfragen, stark davon abhängt, welcher Webserver für die Applikation verwendet wird. Einige Server, z.B. WEBRick, unterstützen Streaming nicht oder nur teilweise. Sollte der Server Streaming nicht unterstützen, wird ein vollständiger Response-Body zurückgeschickt, sobald der an `stream` weitergegebene Block abgearbeitet ist. Mit Shotgun funktioniert Streaming z.B. überhaupt nicht. Ist der optionale Parameter `keep_open` aktiviert, wird beim gestreamten Objekt `close` nicht aufgerufen und es ist einem überlassen dies an einem beliebigen späteren Zeitpunkt nachholen. Die Funktion ist jedoch nur bei Event-gesteuerten Serven wie Thin oder Rainbows möglich, andere Server werden trotzdem den Stream beenden: ```ruby # Durchgehende Anfrage (long polling) set :server, :thin connections = [] get '/subscribe' do # Client-Registrierung beim Server, damit Events mitgeteilt werden können stream(:keep_open) { |out| connections << out } # tote Verbindungen entfernen connections.reject!(&:closed?) # Rückmeldung "Angemeldet" end post '/message' do connections.each do |out| # Den Client über eine neue Nachricht in Kenntnis setzen # notify client that a new message has arrived out << params[:message] << "\n" # Den Client zur erneuten Verbindung auffordern out.close end # Rückmeldung "Mitteiling erhalten" end ``` ### Logger Im Geltungsbereich eines Request stellt die `logger` Helfer-Methode eine `Logger` Instanz zur Verfügung: ```ruby get '/' do logger.info "es passiert gerade etwas" # ... end ``` Der Logger übernimmt dabei automatisch alle im Rack-Handler eingestellten Log-Vorgaben. Ist Loggen ausgeschaltet, gibt die Methode ein Leerobjekt zurück. In den Routen und Filtern muss man sich also nicht weiter darum kümmern. Beachte, dass das Loggen standardmäßig nur für `Sinatra::Application` voreingestellt ist. Wird über `Sinatra::Base` vererbt, muss es erst aktiviert werden: ```ruby class MyApp < Sinatra::Base configure :production, :development do enable :logging end end ``` Damit auch keine Middleware das Logging aktivieren kann, muss die `logging` Einstellung auf `nil` gesetzt werden. Das heißt aber auch, dass `logger` in diesem Fall `nil` zurückgeben wird. Üblicherweise wird das eingesetzt, wenn ein eigener Logger eingerichtet werden soll. Sinatra wird dann verwenden, was in `env['rack.logger']` eingetragen ist. ### Mime-Types Wenn `send_file` oder statische Dateien verwendet werden, kann es vorkommen, dass Sinatra den Mime-Typ nicht kennt. Registriert wird dieser mit `mime_type` per Dateiendung: ```ruby configure do mime_type :foo, 'text/foo' end ``` Es kann aber auch der `content_type`-Helfer verwendet werden: ```ruby get '/' do content_type :foo "foo foo foo" end ``` ### URLs generieren Zum Generieren von URLs sollte die `url`-Helfer-Methode genutzen werden, so z.B. beim Einsatz von Haml: ```ruby %a{:href => url('/foo')} foo ``` Soweit vorhanden, wird Rücksicht auf Proxys und Rack-Router genommen. Diese Methode ist ebenso über das Alias `to` zu erreichen (siehe Beispiel unten). ### Browser-Umleitung Eine Browser-Umleitung kann mithilfe der `redirect`-Helfer-Methode erreicht werden: ```ruby get '/foo' do redirect to('/bar') end ``` Weitere Parameter werden wie Argumente der `halt`-Methode behandelt: ```ruby redirect to('/bar'), 303 redirect 'http://google.com', 'Hier bist du falsch' ``` Ebenso leicht lässt sich ein Schritt zurück mit dem Alias `redirect back` erreichen: ```ruby get '/foo' do "mach was" end get '/bar' do mach_was redirect back end ``` Um Argumente an ein Redirect weiterzugeben, können sie entweder dem Query übergeben: ```ruby redirect to('/bar?summe=42') ``` oder eine Session verwendet werden: ```ruby enable :sessions get '/foo' do session[:secret] = 'foo' redirect to('/bar') end get '/bar' do session[:secret] end ``` ### Cache einsetzen Ein sinnvolles Einstellen von Header-Daten ist die Grundlage für ein ordentliches HTTP-Caching. Der Cache-Control-Header lässt sich ganz einfach einstellen: ```ruby get '/' do cache_control :public "schon gecached!" end ``` Profitipp: Caching im before-Filter aktivieren ```ruby before do cache_control :public, :must_revalidate, :max_age => 60 end ``` Bei Verwendung der `expires`-Helfermethode zum Setzen des gleichnamigen Headers, wird `Cache-Control` automatisch eigestellt: ```ruby before do expires 500, :public, :must_revalidate end ``` Um alles richtig zu machen, sollten auch `etag` oder `last_modified` verwendet werden. Es wird empfohlen, dass diese Helfer aufgerufen werden **bevor** die eigentliche Arbeit anfängt, da sie sofort eine Antwort senden, wenn der Client eine aktuelle Version im Cache vorhält: ```ruby get '/article/:id' do @article = Article.find params[:id] last_modified @article.updated_at etag @article.sha1 erb :article end ``` ebenso ist es möglich einen [schwachen ETag](http://de.wikipedia.org/wiki/HTTP_ETag) zu verwenden: ```ruby etag @article.sha1, :weak ``` Diese Helfer führen nicht das eigentliche Caching aus, sondern geben die dafür notwendigen Informationen an den Cache weiter. Für schnelle Reverse-Proxy Cache-Lösungen bietet sich z.B. [rack-cache](https://github.com/rtomayko/rack-cache) an: ```ruby require "rack/cache" require "sinatra" use Rack::Cache get '/' do cache_control :public, :max_age => 36000 sleep 5 "hello" end ``` Um den `Cache-Control`-Header mit Informationen zu versorgen, verwendet man die `:static_cache_control`-Einstellung (s.u.). Nach RFC 2616 sollte sich die Anwendung anders verhalten, wenn ein If-Match oder ein If-None_match Header auf `*` gesetzt wird in Abhängigkeit davon, ob die Resource bereits existiert. Sinatra geht davon aus, dass Ressourcen bei sicheren Anfragen (z.B. bei get oder Idempotenten Anfragen wie put) bereits existieren, wobei anderen Ressourcen (besipielsweise bei post), als neue Ressourcen behandelt werden. Dieses Verhalten lässt sich mit der `:new_resource` Option ändern: ```ruby get '/create' do etag '', :new_resource => true Article.create erb :new_article end ``` Soll das schwache ETag trotzdem verwendet werden, verwendet man die `:kind` Option: ```ruby etag '', :new_resource => true, :kind => :weak ``` ### Dateien versenden Zum Versenden von Dateien kann die `send_file`-Helfer-Methode verwendet werden: ```ruby get '/' do send_file 'foo.png' end ``` Für `send_file` stehen einige Hash-Optionen zur Verfügung: ```ruby send_file 'foo.png', :type => :jpg ```
filename
Dateiname als Response. Standardwert ist der eigentliche Dateiname.
last_modified
Wert für den Last-Modified-Header, Standardwert ist mtime der Datei.
type
Content-Type, der verwendet werden soll. Wird, wenn nicht angegeben, von der Dateiendung abgeleitet.
disposition
Verwendet für Content-Disposition. Mögliche Werte sind: nil (Standard), :attachment und :inline.
length
Content-Length-Header. Standardwert ist die Dateigröße.
Soweit vom Rack-Handler unterstützt, werden neben der Übertragung über den Ruby-Prozess auch andere Möglichkeiten genutzt. Bei Verwendung der `send_file`-Helfer-Methode kümmert sich Sinatra selbstständig um die Range-Requests. ### Das Request-Objekt Auf das `request`-Objekt der eigehenden Anfrage kann vom Anfrage-Scope aus zugegriffen werden: ```ruby # App läuft unter http://example.com/example get '/foo' do t = %w[text/css text/html application/javascript] request.accept # ['text/html', '*/*'] request.accept? 'text/xml' # true request.preferred_type(t) # 'text/html' request.body # Request-Body des Client (siehe unten) request.scheme # "http" request.script_name # "/example" request.path_info # "/foo" request.port # 80 request.request_method # "GET" request.query_string # "" request.content_length # Länge des request.body request.media_type # Medientypus von request.body request.host # "example.com" request.get? # true (ähnliche Methoden für andere Verben) request.form_data? # false request["irgendein_param"] # Wert von einem Parameter; [] ist die Kurzform für den params Hash request.referrer # Der Referrer des Clients oder '/' request.user_agent # User-Agent (verwendet in der :agent Bedingung) request.cookies # Hash des Browser-Cookies request.xhr? # Ist das hier ein Ajax-Request? request.url # "http://example.com/example/foo" request.path # "/example/foo" request.ip # IP-Adresse des Clients request.secure? # false (true wenn SSL) request.forwarded? # true (Wenn es hinter einem Reverse-Proxy verwendet wird) request.env # vollständiger env-Hash von Rack übergeben end ``` Manche Optionen, wie etwa `script_name` oder `path_info`, sind auch schreibbar: ```ruby before { request.path_info = "/" } get "/" do "Alle Anfragen kommen hier an!" end ``` Der `request.body` ist ein IO- oder StringIO-Objekt: ```ruby post "/api" do request.body.rewind # falls schon jemand davon gelesen hat daten = JSON.parse request.body.read "Hallo #{daten['name']}!" end ``` ### Anhänge Damit der Browser erkennt, dass ein Response gespeichert und nicht im Browser angezeigt werden soll, kann der `attachment`-Helfer verwendet werden: ```ruby get '/' do attachment "Speichern!" end ``` Ebenso kann eine Dateiname als Parameter hinzugefügt werden: ```ruby get '/' do attachment "info.txt" "Speichern!" end ``` ### Umgang mit Datum und Zeit Sinatra bietet eine `time_for`-Helfer-Methode, die aus einem gegebenen Wert ein Time-Objekt generiert. Ebenso kann sie nach `DateTime`, `Date` und ähnliche Klassen konvertieren: ```ruby get '/' do pass if Time.now > time_for('Dec 23, 2012') "noch Zeit" end ``` Diese Methode wird intern für +expires, `last_modiefied` und ihresgleichen verwendet. Mit ein paar Handgriffen lässt sich diese Methode also in ihrem Verhalten erweitern, indem man `time_for` in der eigenen Applikation überschreibt: ```ruby helpers do def time_for(value) case value when :yesterday then Time.now - 24*60*60 when :tomorrow then Time.now + 24*60*60 else super end end end get '/' do last_modified :yesterday expires :tomorrow "Hallo" end ``` ### Nachschlagen von Template-Dateien Die `find_template`-Helfer-Methode wird genutzt, um Template-Dateien zum Rendern aufzufinden: ```ruby find_template settings.views, 'foo', Tilt[:haml] do |file| puts "könnte diese hier sein: #{file}" end ``` Das ist zwar nicht wirklich brauchbar, aber wenn man sie überschreibt, kann sie nützlich werden, um eigene Nachschlage-Mechanismen einzubauen. Zum Beispiel dann, wenn mehr als nur ein view-Verzeichnis verwendet werden soll: ```ruby set :views, ['views', 'templates'] helpers do def find_template(views, name, engine, &block) Array(views).each { |v| super(v, name, engine, &block) } end end ``` Ein anderes Beispiel wäre, verschiedene Vereichnisse für verschiedene Engines zu verwenden: ```ruby set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views' helpers do def find_template(views, name, engine, &block) _, folder = views.detect { |k,v| engine == Tilt[k] } folder ||= views[:default] super(folder, name, engine, &block) end end ``` Ebensogut könnte eine Extension aber auch geschrieben und mit anderen geteilt werden! Beachte, dass `find_template` nicht prüft, ob eine Datei tatsächlich existiert. Es wird lediglich der angegebene Block aufgerufen und nach allen möglichen Pfaden gesucht. Das ergibt kein Performance-Problem, da `render` `block` verwendet, sobald eine Datei gefunden wurde. Ebenso werden Template-Pfade samt Inhalt gecached, solange nicht im Entwicklungsmodus gearbeitet wird. Das sollte im Hinterkopf behalten werden, wenn irgendwelche verrückten Methoden zusammenbastelt werden. ### Konfiguration Wird einmal beim Starten in jedweder Umgebung ausgeführt: ```ruby configure do # setze eine Option set :option, 'wert' # setze mehrere Optionen set :a => 1, :b => 2 # das gleiche wie `set :option, true` enable :option # das gleiche wie `set :option, false` disable :option # dynamische Einstellungen mit Blöcken set(:css_dir) { File.join(views, 'css') } end ``` Läuft nur, wenn die Umgebung (RACK_ENV-Umgebungsvariable) auf `:production` gesetzt ist: ```ruby configure :production do ... end ``` Läuft nur, wenn die Umgebung auf `:production` oder auf `:test` gesetzt ist: ```ruby configure :production, :test do ... end ``` Diese Einstellungen sind über `settings` erreichbar: ```ruby configure do set :foo, 'bar' end get '/' do settings.foo? # => true settings.foo # => 'bar' ... end ``` #### Einstellung des Angriffsschutzes Sinatra verwendet [Rack::Protection](https://github.com/rkh/rack-protection#readme), um die Anwendung vor häufig vorkommenden Angriffen zu schützen. Diese Voreinstellung lässt sich selbstverständlich deaktivieren, der damit verbundene Geschwindigkeitszuwachs steht aber in keinem Verhätnis zu den möglichen Risiken. ```ruby disable :protection ``` Um einen bestimmten Schutzmechanismus zu deaktivieren, fügt man `protection` einen Hash mit Optionen hinzu: ```ruby set :protection, :except => :path_traversal ``` Neben Strings akzeptiert `:except` auch Arrays, um gleich mehrere Schutzmechanismen zu deaktivieren: ```ruby set :protection, :except => [:path_traversal, :session_hijacking] ``` #### Mö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 oder localhost). 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. Die Reihenfolge gibt die Priorität vor, die Voreinstellung hängt von der verwendenten Ruby Implementierung ab.
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]
threaded
Wird es auf true gesetzt, wird Thin aufgefordert EventMachine.defer zur Verarbeitung des Requests einzusetzen.
views
Verzeichnis der Views. Leitet sich von der app_file Einstellung ab, wenn nicht gesetzt.
x_cascade
Einstellung, ob der X-Cascade Header bei fehlender Route gesetzt wird oder nicht. Standardeinstellung ist true.
## 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: ```shell ruby my_app.rb -e [ENVIRONMENT] ``` In der Anwendung kann man die die Methoden `development?`, `test?` und `production?` verwenden, um die aktuelle Umgebung zu erfahren. ## Fehlerbehandlung Error-Handler laufen in demselben Kontext wie Routen und Filter, was bedeutet, dass alle Goodies wie `haml`, `erb`, `halt`, etc. verwendet werden können. ### Nicht gefunden Wenn eine `Sinatra::NotFound`-Exception geworfen wird oder der Statuscode 404 ist, wird der `not_found`-Handler ausgeführt: ```ruby not_found do 'Seite kann nirgendwo gefunden werden.' end ``` ### Fehler Der `error`-Handler wird immer ausgeführt, wenn eine Exception in einem Routen-Block oder in einem Filter geworfen wurde. Die Exception kann über die `sinatra.error`-Rack-Variable angesprochen werden: ```ruby error do 'Entschuldige, es gab einen hässlichen Fehler - ' + env['sinatra.error'].name end ``` Benutzerdefinierte Fehler: ```ruby error MeinFehler do 'Au weia, ' + env['sinatra.error'].message end ``` Dann, wenn das passiert: ```ruby get '/' do raise MeinFehler, 'etwas Schlimmes ist passiert' end ``` bekommt man dieses: ```shell Au weia, etwas Schlimmes ist passiert ``` Alternativ kann ein Error-Handler auch für einen Status-Code definiert werden: ```ruby error 403 do 'Zugriff verboten' end get '/geheim' do 403 end ``` Oder ein Status-Code-Bereich: ```ruby error 400..510 do 'Hallo?' end ``` Sinatra setzt verschiedene `not_found`- und `error`-Handler in der Development-Umgebung ein, um hilfreiche Debugging Informationen und Stack Traces anzuzeigen. ## Rack-Middleware Sinatra baut auf [Rack](http://rack.rubyforge.org/), einem minimalistischen Standard-Interface für Ruby-Webframeworks. Eines der interessantesten Features für Entwickler ist der Support von Middlewares, die zwischen den Server und die Anwendung geschaltet werden und so HTTP-Request und/oder Antwort überwachen und/oder manipulieren können. Sinatra macht das Erstellen von Middleware-Verkettungen mit der Top-Level-Methode `use` zu einem Kinderspiel: ```ruby require 'sinatra' require 'meine_middleware' use Rack::Lint use MeineMiddleware get '/hallo' do 'Hallo Welt' end ``` Die Semantik von `use` entspricht der gleichnamigen Methode der [Rack::Builder](http://rack.rubyforge.org/doc/classes/Rack/Builder.html)-DSL (meist verwendet in Rackup-Dateien). Ein Beispiel dafür ist, dass die `use`-Methode mehrere/verschiedene Argumente und auch Blöcke entgegennimmt: ```ruby use Rack::Auth::Basic do |username, password| username == 'admin' && password == 'geheim' end ``` Rack bietet eine Vielzahl von Standard-Middlewares für Logging, Debugging, URL-Routing, Authentifizierung und Session-Verarbeitung. Sinatra verwendet viele von diesen Komponenten automatisch, abhängig von der Konfiguration. So muss `use` häufig nicht explizit verwendet werden. Hilfreiche Middleware gibt es z.B. hier: [rack](https://github.com/rack/rack/tree/master/lib/rack), [rack-contrib](https://github.com/rack/rack-contrib#readme), mit [CodeRack](http://coderack.org/) oder im [Rack wiki](https://github.com/rack/rack/wiki/List-of-Middleware). ## Testen Sinatra-Tests können mit jedem auf Rack aufbauendem Test-Framework geschrieben werden. [Rack::Test](http://rdoc.info/github/brynary/rack-test/master/frames) wird empfohlen: ```ruby require 'my_sinatra_app' require 'test/unit' require 'rack/test' class MyAppTest < Test::Unit::TestCase include Rack::Test::Methods def app Sinatra::Application end def test_my_default get '/' assert_equal 'Hallo Welt!', last_response.body end def test_with_params get '/meet', :name => 'Frank' assert_equal 'Hallo Frank!', last_response.body end def test_with_rack_env get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' assert_equal "Du verwendest Songbird!", last_response.body end end ``` Hinweis: Wird Sinatra modular verwendet, muss Sinatra::Application mit dem Namen der Applikations-Klasse ersetzt werden. ## Sinatra::Base - Middleware, Bibliotheken und modulare Anwendungen Das Definieren einer Top-Level-Anwendung funktioniert gut für Mikro-Anwendungen, hat aber Nachteile, wenn wiederverwendbare Komponenten wie Middleware, Rails Metal, einfache Bibliotheken mit Server-Komponenten oder auch Sinatra-Erweiterungen geschrieben werden sollen. Das Top-Level geht von einer Konfiguration für eine Mikro-Anwendung aus (wie sie z.B. bei einer einzelnen Anwendungsdatei, `./public` und `./views` Ordner, Logging, Exception-Detail-Seite, usw.). Genau hier kommt `Sinatra::Base` ins Spiel: ```ruby require 'sinatra/base' class MyApp < Sinatra::Base set :sessions, true set :foo, 'bar' get '/' do 'Hallo Welt!' end end ``` Die MyApp-Klasse ist eine unabhängige Rack-Komponente, die als Middleware, Endpunkt oder via Rails Metal verwendet werden kann. Verwendet wird sie durch `use` oder `run` von einer Rackup-`config.ru`-Datei oder als Server-Komponente einer Bibliothek: ```ruby MyApp.run! :host => 'localhost', :port => 9090 ``` Die Methoden der `Sinatra::Base`-Subklasse sind genau dieselben wie die der Top-Level-DSL. Die meisten Top-Level-Anwendungen können mit nur zwei Veränderungen zu `Sinatra::Base` konvertiert werden: * Die Datei sollte `require 'sinatra/base'` anstelle von `require 'sinatra/base'` aufrufen, ansonsten werden alle von Sinatras DSL-Methoden in den Top-Level-Namespace importiert. * Alle Routen, Error-Handler, Filter und Optionen der Applikation müssen in einer Subklasse von `Sinatra::Base` definiert werden. `Sinatra::Base` ist ein unbeschriebenes Blatt. Die meisten Optionen sind per Standard deaktiviert. Das betrifft auch den eingebauten Server. Siehe [Optionen und Konfiguration](http://sinatra.github.com/configuration.html) für Details über mögliche Optionen. ### Modularer vs. klassischer Stil Entgegen häufiger Meinungen gibt es nichts gegen den klassischen Stil einzuwenden. Solange es die Applikation nicht beeinträchtigt, besteht kein Grund, eine modulare Applikation zu erstellen. Der größte Nachteil der klassischen Sinatra Anwendung gegenüber einer modularen ist die Einschränkung auf eine Sinatra Anwendung pro Ruby-Prozess. Sollen mehrere zum Einsatz kommen, muss auf den modularen Stil umgestiegen werden. Dabei ist es kein Problem klassische und modulare Anwendungen miteinander zu vermischen. Bei einem Umstieg, sollten einige Unterschiede in den Einstellungen beachtet werden:
Szenario Classic Modular
app_file Sinatra ladende Datei Sinatra::Base subklassierende Datei
run $0 == app_file false
logging true false
method_override true false
inline_templates true false
static true false
### Eine modulare Applikation bereitstellen Es gibt zwei übliche Wege, eine modulare Anwendung zu starten. Zum einen über `run!`: ```ruby # mein_app.rb require 'sinatra/base' class MeinApp < Sinatra::Base # ... Anwendungscode hierhin ... # starte den Server, wenn die Ruby-Datei direkt ausgeführt wird run! if app_file == $0 end ``` Starte mit: ```shell ruby mein_app.rb ``` Oder über eine `config.ru`-Datei, die es erlaubt, einen beliebigen Rack-Handler zu verwenden: ```ruby # config.ru (mit rackup starten) require './mein_app' run MeineApp ``` Starte: ```shell rackup -p 4567 ``` ### Eine klassische Anwendung mit einer config.ru verwenden Schreibe eine Anwendungsdatei: ```ruby # app.rb require 'sinatra' get '/' do 'Hallo Welt!' end ``` sowie eine dazugehörige `config.ru`-Datei: ```ruby require './app' run Sinatra::Application ``` ### Wann sollte eine config.ru-Datei verwendet werden? Anzeichen dafür, dass eine `config.ru`-Datei gebraucht wird: * Es soll ein anderer Rack-Handler verwendet werden (Passenger, Unicorn, Heroku, ...). * Es gibt mehr als nur eine Subklasse von `Sinatra::Base`. * Sinatra soll als Middleware verwendet werden, nicht als Endpunkt. **Es gibt keinen Grund, eine `config.ru`-Datei zu verwenden, nur weil eine Anwendung im modularen Stil betrieben werden soll. Ebenso wird keine Anwendung mit modularem Stil benötigt, um eine `config.ru`-Datei zu verwenden.** ### Sinatra als Middleware nutzen Es ist nicht nur möglich, andere Rack-Middleware mit Sinatra zu nutzen, es kann außerdem jede Sinatra-Anwendung selbst als Middleware vor jeden beliebigen Rack-Endpunkt gehangen werden. Bei diesem Endpunkt muss es sich nicht um eine andere Sinatra-Anwendung handeln, es kann jede andere Rack-Anwendung sein (Rails/Ramaze/Camping/...): ```ruby require 'sinatra/base' class LoginScreen < Sinatra::Base enable :sessions get('/login') { haml :login } post('/login') do if params[:name] == 'admin' && params[:password] == 'admin' session['user_name'] = params[:name] else redirect '/login' end end end class MyApp < Sinatra::Base # Middleware wird vor Filtern ausgeführt use LoginScreen before do unless session['user_name'] halt "Zugriff verweigert, bitte einloggen." end end get('/') { "Hallo #{session['user_name']}." } end ``` ### Dynamische Applikationserstellung Manche Situationen erfordern die Erstellung neuer Applikationen zur Laufzeit, ohne dass sie einer Konstanten zugeordnet werden. Dies lässt sich mit `Sinatra.new` erreichen: ```ruby require 'sinatra/base' my_app = Sinatra.new { get('/') { "hallo" } } my_app.run! ``` Die Applikation kann mit Hilfe eines optionalen Parameters erstellt werden: ```ruby # config.ru require 'sinatra/base' controller = Sinatra.new do enable :logging helpers MyHelpers end map('/a') do run Sinatra.new(controller) { get('/') { 'a' } } end map('/b') do run Sinatra.new(controller) { get('/') { 'b' } } end ``` Das ist besonders dann interessant, wenn Sinatra-Erweiterungen getestet werden oder Sinatra in einer Bibliothek Verwendung findet. Ebenso lassen sich damit hervorragend Sinatra-Middlewares erstellen: ```ruby require 'sinatra/base' use Sinatra do get('/') { ... } end run RailsProject::Application ``` ## Geltungsbereich und Bindung Der Geltungsbereich (Scope) legt fest, welche Methoden und Variablen zur Verfügung stehen. ### Anwendungs- oder Klassen-Scope Jede Sinatra-Anwendung entspricht einer `Sinatra::Base`-Subklasse. Falls die Top- Level-DSL verwendet wird (`require 'sinatra'`), handelt es sich um `Sinatra::Application`, andernfalls ist es jene Subklasse, die explizit angelegt wurde. Auf Klassenebene stehen Methoden wie `get` oder `before` zur Verfügung, es gibt aber keinen Zugriff auf das `request`-Object oder die `session`, da nur eine einzige Klasse für alle eingehenden Anfragen genutzt wird. Optionen, die via `set` gesetzt werden, sind Methoden auf Klassenebene: ```ruby class MyApp < Sinatra::Base # Hey, ich bin im Anwendungsscope! set :foo, 42 foo # => 42 get '/foo' do # Hey, ich bin nicht mehr im Anwendungs-Scope! end end ``` Im Anwendungs-Scope befindet man sich: * In der Anwendungs-Klasse. * In Methoden, die von Erweiterungen definiert werden. * Im Block, der an `helpers` übergeben wird. * In Procs und Blöcken, die an `set` übergeben werden. * Der an `Sinatra.new` übergebene Block Auf das Scope-Objekt (die Klasse) kann wie folgt zugegriffen werden: * Über das Objekt, das an den `configure`-Block übergeben wird (`configure { |c| ... }`). * `settings` aus den anderen Scopes heraus. ### Anfrage- oder Instanz-Scope Für jede eingehende Anfrage wird eine neue Instanz der Anwendungs-Klasse erstellt und alle Handler in diesem Scope ausgeführt. Aus diesem Scope heraus kann auf `request` oder `session` zugegriffen und Methoden wie `erb` oder `haml` aufgerufen werden. Außerdem kann mit der `settings`-Method auf den Anwendungs-Scope zugegriffen werden: ```ruby class MyApp < Sinatra::Base # Hey, ich bin im Anwendungs-Scope! get '/neue_route/:name' do # Anfrage-Scope für '/neue_route/:name' @value = 42 settings.get "/#{params[:name]}" do # Anfrage-Scope für "/#{params[:name]}" @value # => nil (nicht dieselbe Anfrage) end "Route definiert!" end end ``` Im Anfrage-Scope befindet man sich: * In get, head, post, put, delete, options, patch, link und unlink Blöcken * In before und after Filtern * In Helfer-Methoden * In Templates ### Delegation-Scope Vom Delegation-Scope aus werden Methoden einfach an den Klassen-Scope weitergeleitet. Dieser verhält sich jedoch nicht 100%ig wie der Klassen-Scope, da man nicht die Bindung der Klasse besitzt: Nur Methoden, die explizit als delegierbar markiert wurden, stehen hier zur Verfügung und es kann nicht auf die Variablen des Klassenscopes zugegriffen werden (mit anderen Worten: es gibt ein anderes `self`). Weitere Delegationen können mit `Sinatra::Delegator.delegate :methoden_name` hinzugefügt werden. Im Delegation-Scop befindet man sich: * Im Top-Level, wenn `require 'sinatra'` aufgerufen wurde. * In einem Objekt, das mit dem `Sinatra::Delegator`-Mixin erweitert wurde. Schau am besten im Code nach: Hier ist [Sinatra::Delegator mixin](http://github.com/sinatra/sinatra/blob/master/lib/sinatra/base.rb#L1064 ) definiert und wird in den [globalen Namespace eingebunden](http://github.com/sinatra/sinatra/blob/master/lib/sinatra/main.rb ## Kommandozeile Sinatra-Anwendungen können direkt von der Kommandozeile aus gestartet werden: ```shell 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, ein Wechsel zu JRuby oder Rubinius wird aber empfohlen. Ruby 1.8.7 wird noch bis Sinatra 2.0 unterstützt werden. Frühere Versionen von Ruby sind nicht kompatibel mit Sinatra.
Ruby 1.9.2
1.9.2 wird mindestens bis Sinatra 1.5 voll unterstützt. Version 1.9.2p0 sollte nicht verwendet werden, da unter Sinatra immer wieder Segfaults auftreten.
Ruby 1.9.3
1.9.3 wird vollständig unterstützt und empfohlen. Achtung, bei einem Upgrade von einer früheren Version von Ruby zu Ruby 1.9.3 werden alle Sessions ungültig. Ruby 1.9.3 wird bis Sinatra 2.0 unterstützt werden.
Rubinius
Rubinius (Version >= 2.x) wird offiziell unterstützt. Es wird empfohlen, den Puma Server zu installieren (gem install puma )
JRuby
Aktuelle JRuby Versionen werden offiziell unterstützt. Es wird empfohlen, keine C-Erweiterungen zu verwenden und als Server Trinidad zu verwenden (gem install trinidad).
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 (gem install control_tower wird empfohlen), Maglev, IronRuby * Ruby 1.9.0 und 1.9.1 Nicht offiziell unterstützt bedeutet, dass wenn Sachen nicht funktionieren, wir davon ausgehen, dass es nicht an Sinatra sondern an der jeweiligen Implementierung liegt. Im Rahmen unserer CI (Kontinuierlichen Integration) wird bereits ruby-head (das kommende Ruby 2.1.0) mit eingebunden. Es kann davon ausgegangen werden, dass Sinatra Ruby 2.1.0 vollständig unterstützen wird. Sinatra sollte auf jedem Betriebssystem laufen, dass einen funktionierenden Ruby-Interpreter aufweist. Sinatra läuft aktuell nicht unter Cardinal, SmallRuby, BlueRuby oder Ruby <= 1.8.7. ## 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: ```shell 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: ```shell gem install bundler ``` Anschließend wird eine `Gemfile`-Datei im Projektverzeichnis mit folgendem Inhalt erstellt: ```ruby source :rubygems gem 'sinatra', :git => "git://github.com/sinatra/sinatra.git" # evtl. andere Abhängigkeiten gem 'haml' # z.B. wenn du Haml verwendest... gem 'activerecord', '~> 3.0' # ...oder ActiveRecord 3.x ``` Beachte: Hier sollten alle Abhängigkeiten eingetragen werden. Sinatras eigene, direkte Abhängigkeiten (Tilt und Rack) werden von Bundler automatisch aus dem Gemfile von Sinatra hinzugefügt. Jetzt kannst du deine Applikation starten: ```shell 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`: ```shell cd myapp git clone git://github.com/sinatra/sinatra.git ruby -Isinatra/lib myapp.rb ``` Alternativ kann der `sinatra/lib`-Ordner zum `LOAD_PATH` in der Anwendung hinzugefügt werden: ```ruby $LOAD_PATH.unshift File.dirname(__FILE__) + '/sinatra/lib' require 'rubygems' require 'sinatra' get '/ueber' do "Ich laufe auf Version " + Sinatra::VERSION end ``` Um Sinatra-Code von Zeit zu Zeit zu aktualisieren: ```shell cd myproject/sinatra git pull ``` ### Gem erstellen Aus der eigenen lokalen Kopie kann nun auch ein globales Gem gebaut werden: ```shell 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: ```shell sudo rake install ``` ## Versions-Verfahren Sinatra folgt dem sogenannten [Semantic Versioning](http://semver.org/), d.h. SemVer und SemVerTag. ## Mehr * [Projekt-Website](http://sinatra.github.com/) - Ergänzende Dokumentation, News und Links zu anderen Ressourcen. * [Mitmachen](http://sinatra.github.com/contributing.html) - Einen Fehler gefunden? Brauchst du Hilfe? Hast du einen Patch? * [Issue-Tracker](http://github.com/sinatra/sinatra/issues) * [Twitter](http://twitter.com/sinatra) * [Mailing-Liste](http://groups.google.com/group/sinatrarb) * [#sinatra](irc://chat.freenode.net/#sinatra) auf http://freenode.net Es gibt dort auch immer wieder deutschsprachige Entwickler, die gerne weiterhelfen. * [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) sinatra-1.4.3/checksums.yaml.gz0000444000004100000410000000041412161612726016501 0ustar www-datawww-data*KQe;r@=^`]|A2> ȉKOB;=_矷k$ydcv{\Fz_GbDٌp4M'`vޟUE YUJMu>'{stb-G,=~vDJXLjl T.!_fab8OYEuPJI{ڬkY( GyuөIeYxd4ٰM2 T{w1LԈn/sinatra-1.4.3/README.zh.md0000644000004100000410000013561612161612727015130 0ustar www-datawww-data# Sinatra *注:本文档仅仅是英文版的翻译,会出现内容没有及时更新的情况发生。 如有不一致的地方,请以英文版为准。* Sinatra是一个基于Ruby语言,以最小精力为代价快速创建web应用为目的的[DSL](http://en.wikipedia.org/wiki/Domain-specific_language)( 领域专属语言): ~~~~ ruby # myapp.rb require 'sinatra' get '/' do 'Hello world!' end ~~~~ 安装gem然后运行: ~~~~ shell gem install sinatra ruby myapp.rb ~~~~ 在该地址查看: [localhost:4567](http://localhost:4567) 推荐同时运行`gem install thin`,Sinatra会优先选择thin作为服务器。 ## 路由 在Sinatra中,一个路由是一个HTTP方法与URL匹配范式的配对。 每个路由都与一个代码块关联: ~~~~ ruby get '/' do .. 显示一些事物 .. end post '/' do .. 创建一些事物 .. end put '/' do .. 更新一些事物 .. end delete '/' do .. 消灭一些事物 .. end options '/' do .. 满足一些事物 .. end ~~~~ 路由按照它们被定义的顺序进行匹配。 第一个与请求匹配的路由会被调用。 路由范式可以包括具名参数,可通过`params`哈希表获得: ~~~~ ruby get '/hello/:name' do # 匹配 "GET /hello/foo" 和 "GET /hello/bar" # params[:name] 的值是 'foo' 或者 'bar' "Hello #{params[:name]}!" end ~~~~ 你同样可以通过代码块参数获得具名参数: ~~~~ ruby get '/hello/:name' do |n| "Hello #{n}!" end ~~~~ 路由范式也可以包含通配符参数, 可以通过`params[:splat]`数组获得。 ~~~~ ruby get '/say/*/to/*' do # 匹配 /say/hello/to/world params[:splat] # => ["hello", "world"] end get '/download/*.*' do # 匹配 /download/path/to/file.xml params[:splat] # => ["path/to/file", "xml"] end ~~~~ 通过正则表达式匹配的路由: ~~~~ ruby get %r{/hello/([\w]+)} do "Hello, #{params[:captures].first}!" end ~~~~ 或者使用代码块参数: ~~~~ ruby get %r{/hello/([\w]+)} do |c| "Hello, #{c}!" end ~~~~ ### 条件 路由也可以包含多样的匹配条件,比如user agent: ~~~~ ruby get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do "你正在使用Songbird,版本是 #{params[:agent][0]}" end get '/foo' do # 匹配除Songbird以外的浏览器 end ~~~~ 其他可选的条件是 `host_name` 和 `provides`: ~~~~ ruby get '/', :host_name => /^admin\./ do "管理员区域,无权进入!" end get '/', :provides => 'html' do haml :index end get '/', :provides => ['rss', 'atom', 'xml'] do builder :feed end ~~~~ 你也可以很轻松地定义自己的条件: ~~~~ ruby set(:probability) { |value| condition { rand <= value } } get '/win_a_car', :probability => 0.1 do "You won!" end get '/win_a_car' do "Sorry, you lost." end ~~~~ ### 返回值 路由代码块的返回值至少决定了返回给HTTP客户端的响应体, 或者至少决定了在Rack堆栈中的下一个中间件。 大多数情况下,将是一个字符串,就像上面的例子中的一样。 但是其他值也是可以接受的。 你可以返回任何对象,或者是一个合理的Rack响应, Rack body对象或者HTTP状态码: - 一个包含三个元素的数组: `[状态 (Fixnum), 头 (Hash), 响应体 (回应 #each)]` - 一个包含两个元素的数组: `[状态 (Fixnum), 响应体 (回应 #each)]` - 一个能够回应 `#each` ,只传回字符串的对象 - 一个代表状态码的数字 那样,我们可以轻松的实现例如流式传输的例子: ~~~~ ruby class Stream def each 100.times { |i| yield "#{i}\n" } end end get('/') { Stream.new } ~~~~ ### 自定义路由匹配器 如上显示,Sinatra内置了对于使用字符串和正则表达式作为路由匹配的支持。 但是,它并没有只限于此。 你可以非常容易地定义你自己的匹配器: ~~~~ ruby class AllButPattern Match = Struct.new(:captures) def initialize(except) @except = except @captures = Match.new([]) end def match(str) @captures unless @except === str end end def all_but(pattern) AllButPattern.new(pattern) end get all_but("/index") do # ... end ~~~~ 请注意上面的例子可能超工程了, 因为它也可以用更简单的方式表述: ~~~~ ruby get // do pass if request.path_info == "/index" # ... end ~~~~ 或者,使用消极向前查找: ~~~~ ruby get %r{^(?!/index$)} do # ... end ~~~~ ## 静态文件 静态文件是从 `./public_folder` 目录提供服务。你可以通过设置`:public` 选项设定一个不同的位置: ~~~~ ruby set :public_folder, File.dirname(__FILE__) + '/static' ~~~~ 请注意public目录名并没有被包含在URL之中。文件 `./public/css/style.css`是通过 `http://example.com/css/style.css`地址访问的。 ## 视图 / 模板 模板被假定直接位于`./views`目录。 要使用不同的视图目录: ~~~~ ruby set :views, File.dirname(__FILE__) + '/templates' ~~~~ 请记住一件非常重要的事情,你只可以通过符号引用模板, 即使它们在子目录下 (在这种情况下,使用 `:'subdir/template'`)。 你必须使用一个符号, 因为渲染方法会直接地渲染任何传入的字符串。 ### Haml模板 需要引入 `haml` gem/library以渲染 HAML 模板: ~~~~ ruby # 你需要在你的应用中引入 haml require 'haml' get '/' do haml :index end ~~~~ 渲染 `./views/index.haml`。 [Haml的选项](http://haml.info/docs/yardoc/file.HAML_REFERENCE.html#options) 可以通过Sinatra的配置全局设定, 参见 [选项和配置](http://www.sinatrarb.com/configuration.html), 也可以个别的被覆盖。 ~~~~ ruby set :haml, {:format => :html5 } # 默认的Haml输出格式是 :xhtml get '/' do haml :index, :haml_options => {:format => :html4 } # 被覆盖,变成:html4 end ~~~~ ### Erb模板 ~~~~ ruby # 你需要在你的应用中引入 erb require 'erb' get '/' do erb :index end ~~~~ 渲染 `./views/index.erb` ### Erubis 需要引入 `erubis` gem/library以渲染 erubis 模板: ~~~~ ruby # 你需要在你的应用中引入 erubis require 'erubis' get '/' do erubis :index end ~~~~ 渲染 `./views/index.erubis` 使用Erubis代替Erb也是可能的: ~~~~ ruby require 'erubis' Tilt.register :erb, Tilt[:erubis] get '/' do erb :index end ~~~~ 使用Erubis来渲染 `./views/index.erb`。 ### Builder 模板 需要引入 `builder` gem/library 以渲染 builder templates: ~~~~ ruby # 需要在你的应用中引入builder require 'builder' get '/' do builder :index end ~~~~ 渲染 `./views/index.builder`。 ### Nokogiri 模板 需要引入 `nokogiri` gem/library 以渲染 nokogiri 模板: ~~~~ ruby # 需要在你的应用中引入 nokogiri require 'nokogiri' get '/' do nokogiri :index end ~~~~ 渲染 `./views/index.nokogiri`。 ### Sass 模板 需要引入 `haml` 或者 `sass` gem/library 以渲染 Sass 模板: ~~~~ ruby # 需要在你的应用中引入 haml 或者 sass require 'sass' get '/stylesheet.css' do sass :stylesheet end ~~~~ 渲染 `./views/stylesheet.sass`。 [Sass 的选项](http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#options) 可以通过Sinatra选项全局设定, 参考 [选项和配置(英文)](http://www.sinatrarb.com/configuration.html), 也可以在个体的基础上覆盖。 ~~~~ ruby set :sass, {:style => :compact } # 默认的 Sass 样式是 :nested get '/stylesheet.css' do sass :stylesheet, :style => :expanded # 覆盖 end ~~~~ ### Scss 模板 需要引入 `haml` 或者 `sass` gem/library 来渲染 Scss templates: ~~~~ ruby # 需要在你的应用中引入 haml 或者 sass require 'sass' get '/stylesheet.css' do scss :stylesheet end ~~~~ 渲染 `./views/stylesheet.scss`。 [Scss的选项](http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#options) 可以通过Sinatra选项全局设定, 参考 [选项和配置(英文)](http://www.sinatrarb.com/configuration.html), 也可以在个体的基础上覆盖。 ~~~~ ruby set :scss, :style => :compact # default Scss style is :nested get '/stylesheet.css' do scss :stylesheet, :style => :expanded # overridden end ~~~~ ### Less 模板 需要引入 `less` gem/library 以渲染 Less 模板: ~~~~ ruby # 需要在你的应用中引入 less require 'less' get '/stylesheet.css' do less :stylesheet end ~~~~ 渲染 `./views/stylesheet.less`。 ### Liquid 模板 需要引入 `liquid` gem/library 来渲染 Liquid 模板: ~~~~ ruby # 需要在你的应用中引入 liquid require 'liquid' get '/' do liquid :index end ~~~~ 渲染 `./views/index.liquid`。 因为你不能在Liquid 模板中调用 Ruby 方法 (除了 `yield`) , 你几乎总是需要传递locals给它: ~~~~ ruby liquid :index, :locals => { :key => 'value' } ~~~~ ### Markdown 模板 需要引入 `rdiscount` gem/library 以渲染 Markdown 模板: ~~~~ ruby # 需要在你的应用中引入rdiscount require "rdiscount" get '/' do markdown :index end ~~~~ 渲染 `./views/index.markdown` (`md` 和 `mkd` 也是合理的文件扩展名)。 在markdown中是不可以调用方法的,也不可以传递 locals给它。 你因此一般会结合其他的渲染引擎来使用它: ~~~~ ruby erb :overview, :locals => { :text => markdown(:introduction) } ~~~~ 请注意你也可以从其他模板中调用 markdown 方法: ~~~~ ruby %h1 Hello From Haml! %p= markdown(:greetings) ~~~~ 既然你不能在Markdown中调用Ruby,你不能使用Markdown编写的布局。 不过,使用其他渲染引擎作为模版的布局是可能的, 通过传递`:layout_engine`选项: ~~~~ ruby get '/' do markdown :index, :layout_engine => :erb end ~~~~ 这将会渲染 `./views/index.md` 并使用 `./views/layout.erb` 作为布局。 请记住你可以全局设定这个选项: ~~~~ ruby set :markdown, :layout_engine => :haml, :layout => :post get '/' do markdown :index end ~~~~ 这将会渲染 `./views/index.markdown` (和任何其他的 Markdown 模版) 并使用 `./views/post.haml` 作为布局. 也可能使用BlueCloth而不是RDiscount来解析Markdown文件: ~~~~ ruby require 'bluecloth' Tilt.register 'markdown', BlueClothTemplate Tilt.register 'mkd', BlueClothTemplate Tilt.register 'md', BlueClothTemplate get '/' do markdown :index end ~~~~ 使用BlueCloth来渲染 `./views/index.md` 。 ### Textile 模板 需要引入 `RedCloth` gem/library 以渲染 Textile 模板: ~~~~ ruby # 在你的应用中引入redcloth require "redcloth" get '/' do textile :index end ~~~~ 渲染 `./views/index.textile`。 在textile中是不可以调用方法的,也不可以传递 locals给它。 你因此一般会结合其他的渲染引擎来使用它: ~~~~ ruby erb :overview, :locals => { :text => textile(:introduction) } ~~~~ 请注意你也可以从其他模板中调用`textile`方法: ~~~~ ruby %h1 Hello From Haml! %p= textile(:greetings) ~~~~ 既然你不能在Textile中调用Ruby,你不能使用Textile编写的布局。 不过,使用其他渲染引擎作为模版的布局是可能的, 通过传递`:layout_engine`选项: ~~~~ ruby get '/' do textile :index, :layout_engine => :erb end ~~~~ 这将会渲染 `./views/index.textile` 并使用 `./views/layout.erb` 作为布局。 请记住你可以全局设定这个选项: ~~~~ ruby set :textile, :layout_engine => :haml, :layout => :post get '/' do textile :index end ~~~~ 这将会渲染 `./views/index.textile` (和任何其他的 Textile 模版) 并使用 `./views/post.haml` 作为布局. ### RDoc 模板 需要引入 `RDoc` gem/library 以渲染RDoc模板: ~~~~ ruby # 需要在你的应用中引入rdoc/markup/to_html require "rdoc" require "rdoc/markup/to_html" get '/' do rdoc :index end ~~~~ 渲染 `./views/index.rdoc`。 在rdoc中是不可以调用方法的,也不可以传递locals给它。 你因此一般会结合其他的渲染引擎来使用它: ~~~~ ruby erb :overview, :locals => { :text => rdoc(:introduction) } ~~~~ 请注意你也可以从其他模板中调用`rdoc`方法: ~~~~ ruby %h1 Hello From Haml! %p= rdoc(:greetings) ~~~~ 既然你不能在RDoc中调用Ruby,你不能使用RDoc编写的布局。 不过,使用其他渲染引擎作为模版的布局是可能的, 通过传递`:layout_engine`选项: ~~~~ ruby get '/' do rdoc :index, :layout_engine => :erb end ~~~~ 这将会渲染 `./views/index.rdoc` 并使用 `./views/layout.erb` 作为布局。 请记住你可以全局设定这个选项: ~~~~ ruby set :rdoc, :layout_engine => :haml, :layout => :post get '/' do rdoc :index end ~~~~ 这将会渲染 `./views/index.rdoc` (和任何其他的 RDoc 模版) 并使用 `./views/post.haml` 作为布局. ### Radius 模板 需要引入 `radius` gem/library 以渲染 Radius 模板: ~~~~ ruby # 需要在你的应用中引入radius require 'radius' get '/' do radius :index end ~~~~ 渲染 `./views/index.radius`。 因为你不能在Radius 模板中调用 Ruby 方法 (除了 `yield`) , 你几乎总是需要传递locals给它: ~~~~ ruby radius :index, :locals => { :key => 'value' } ~~~~ ### Markaby 模板 需要引入`markaby` gem/library以渲染Markaby模板: ~~~~ ruby #需要在你的应用中引入 markaby require 'markaby' get '/' do markaby :index end ~~~~ 渲染 `./views/index.mab`。 你也可以使用嵌入的 Markaby: ~~~~ ruby get '/' do markaby { h1 "Welcome!" } end ~~~~ ### Slim 模板 需要引入 `slim` gem/library 来渲染 Slim 模板: ~~~~ ruby # 需要在你的应用中引入 slim require 'slim' get '/' do slim :index end ~~~~ 渲染 `./views/index.slim`。 ### Creole 模板 需要引入 `creole` gem/library 来渲染 Creole 模板: ~~~~ ruby # 需要在你的应用中引入 creole require 'creole' get '/' do creole :index end ~~~~ 渲染 `./views/index.creole`。 ### CoffeeScript 模板 需要引入 `coffee-script` gem/library 并至少满足下面条件一项 以执行Javascript: - `node` (来自 Node.js) 在你的路径中 - 你正在运行 OSX - `therubyracer` gem/library 请察看 [github.com/josh/ruby-coffee-script](http://github.com/josh/ruby-coffee-script) 获取更新的选项。 现在你可以渲染 CoffeeScript 模版了: ~~~~ ruby # 需要在你的应用中引入coffee-script require 'coffee-script' get '/application.js' do coffee :application end ~~~~ 渲染 `./views/application.coffee`。 ### 嵌入模板字符串 ~~~~ ruby get '/' do haml '%div.title Hello World' end ~~~~ 渲染嵌入模板字符串。 ### 在模板中访问变量 模板和路由执行器在同样的上下文求值。 在路由执行器中赋值的实例变量可以直接被模板访问。 ~~~~ ruby get '/:id' do @foo = Foo.find(params[:id]) haml '%h1= @foo.name' end ~~~~ 或者,显式地指定一个本地变量的哈希: ~~~~ ruby get '/:id' do foo = Foo.find(params[:id]) haml '%h1= foo.name', :locals => { :foo => foo } end ~~~~ 典型的使用情况是在别的模板中按照局部模板的方式来渲染。 ### 内联模板 模板可以在源文件的末尾定义: ~~~~ ruby require 'sinatra' get '/' do haml :index end __END__ @@ layout %html = yield @@ index %div.title Hello world!!!!! ~~~~ 注意:引入sinatra的源文件中定义的内联模板才能被自动载入。 如果你在其他源文件中有内联模板, 需要显式执行调用`enable :inline_templates`。 ### 具名模板 模板可以通过使用顶层 `template` 方法定义: ~~~~ ruby template :layout do "%html\n =yield\n" end template :index do '%div.title Hello World!' end get '/' do haml :index end ~~~~ 如果存在名为“layout”的模板,该模板会在每个模板渲染的时候被使用。 你可以单独地通过传送 `:layout => false`来禁用, 或者通过`set :haml, :layout => false`来禁用他们。 ~~~~ ruby get '/' do haml :index, :layout => !request.xhr? end ~~~~ ### 关联文件扩展名 为了关联一个文件扩展名到一个模版引擎,使用 `Tilt.register`。比如,如果你喜欢使用 `tt` 作为Textile模版的扩展名,你可以这样做: ~~~~ ruby Tilt.register :tt, Tilt[:textile] ~~~~ ### 添加你自己的模版引擎 首先,通过Tilt注册你自己的引擎,然后创建一个渲染方法: ~~~~ ruby Tilt.register :myat, MyAwesomeTemplateEngine helpers do def myat(*args) render(:myat, *args) end end get '/' do myat :index end ~~~~ 渲染 `./views/index.myat`。察看 [github.com/rtomayko/tilt](https://github.com/rtomayko/tilt) 来更多了解Tilt. ## 过滤器 前置过滤器在每个请求前,在请求的上下文环境中被执行, 而且可以修改请求和响应。 在过滤器中设定的实例变量可以被路由和模板访问: ~~~~ ruby before do @note = 'Hi!' request.path_info = '/foo/bar/baz' end get '/foo/*' do @note #=> 'Hi!' params[:splat] #=> 'bar/baz' end ~~~~ 后置过滤器在每个请求之后,在请求的上下文环境中执行, 而且可以修改请求和响应。 在前置过滤器和路由中设定的实例变量可以被后置过滤器访问: ~~~~ ruby after do puts response.status end ~~~~ 请注意:除非你显式使用 `body` 方法,而不是在路由中直接返回字符串, 消息体在后置过滤器是不可用的, 因为它在之后才会生成。 过滤器可以可选地带有范式, 只有请求路径满足该范式时才会执行: ~~~~ ruby before '/protected/*' do authenticate! end after '/create/:slug' do |slug| session[:last_slug] = slug end ~~~~ 和路由一样,过滤器也可以带有条件: ~~~~ ruby before :agent => /Songbird/ do # ... end after '/blog/*', :host_name => 'example.com' do # ... end ~~~~ ## 辅助方法 使用顶层的 `helpers` 方法来定义辅助方法, 以便在路由处理器和模板中使用: ~~~~ ruby helpers do def bar(name) "#{name}bar" end end get '/:name' do bar(params[:name]) end ~~~~ ### 使用 Sessions Session被用来在请求之间保持状态。如果被激活,每一个用户会话 对应有一个session哈希: ~~~~ ruby enable :sessions get '/' do "value = " << session[:value].inspect end get '/:value' do session[:value] = params[:value] end ~~~~ 请注意 `enable :sessions` 实际上保存所有的数据在一个cookie之中。 这可能不会总是做你想要的(比如,保存大量的数据会增加你的流量)。 你可以使用任何的Rack session中间件,为了这么做, \*不要\*调用 `enable :sessions`,而是 按照自己的需要引入你的中间件: ~~~~ ruby use Rack::Session::Pool, :expire_after => 2592000 get '/' do "value = " << session[:value].inspect end get '/:value' do session[:value] = params[:value] end ~~~~ ### 挂起 要想直接地停止请求,在过滤器或者路由中使用: ~~~~ ruby halt ~~~~ 你也可以指定挂起时的状态码: ~~~~ ruby halt 410 ~~~~ 或者消息体: ~~~~ ruby halt 'this will be the body' ~~~~ 或者两者; ~~~~ ruby halt 401, 'go away!' ~~~~ 也可以带消息头: ~~~~ ruby halt 402, {'Content-Type' => 'text/plain'}, 'revenge' ~~~~ ### 让路 一个路由可以放弃处理,将处理让给下一个匹配的路由,使用 `pass`: ~~~~ ruby get '/guess/:who' do pass unless params[:who] == 'Frank' 'You got me!' end get '/guess/*' do 'You missed!' end ~~~~ 路由代码块被直接退出,控制流继续前进到下一个匹配的路由。 如果没有匹配的路由,将返回404。 ### 触发另一个路由 有些时候,`pass` 并不是你想要的,你希望得到的是另一个路由的结果 。简单的使用 `call` 可以做到这一点: ~~~~ ruby get '/foo' do status, headers, body = call env.merge("PATH_INFO" => '/bar') [status, headers, body.map(&:upcase)] end get '/bar' do "bar" end ~~~~ 请注意在以上例子中,你可以更加简化测试并增加性能,只要简单的移动 "bar"到一个被/foo 和 `/bar`同时使用的helper。 如果你希望请求被发送到同一个应用,而不是副本, 使用 `call!` 而不是 `call`. 察看 Rack specification 如果你想更多了解 `call`. ### 设定 消息体,状态码和消息头 通过路由代码块的返回值来设定状态码和消息体不仅是可能的,而且是推荐的。 但是,在某些场景中你可能想在作业流程中的特定点上设置消息体。 你可以通过 `body` 辅助方法这么做。 如果你这样做了, 你可以在那以后使用该方法获得消息体: ~~~~ ruby get '/foo' do body "bar" end after do puts body end ~~~~ 也可以传一个代码块给 `body`,它将会被Rack处理器执行( 这将可以被用来实现streaming,参见“返回值”)。 和消息体类似,你也可以设定状态码和消息头: ~~~~ ruby get '/foo' do status 418 headers \ "Allow" => "BREW, POST, GET, PROPFIND, WHEN", "Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt" body "I'm a tea pot!" end ~~~~ 如同 `body`, 不带参数的 `headers` 和 `status` 可以用来访问 他们你的当前值. ### 媒体类型 当使用 `send_file` 或者静态文件的场合,你的媒体类型可能 Sinatra并不理解。使用 `mime_type` 通过文件扩展名来注册它们: ~~~~ ruby mime_type :foo, 'text/foo' ~~~~ 你也可以通过 `content_type` 辅助方法使用: ~~~~ ruby get '/' do content_type :foo "foo foo foo" end ~~~~ ### 生成 URL 为了生成URL,你需要使用 `url` 辅助方法, 例如,在Haml中: ~~~~ ruby %a{:href => url('/foo')} foo ~~~~ 它会根据反向代理和Rack路由,如果有的话,来计算生成的URL。 这个方法还有一个别名 `to` (见下面的例子). ### 浏览器重定向 你可以通过 `redirect` 辅助方法触发浏览器重定向: ~~~~ ruby get '/foo' do redirect to('/bar') end ~~~~ 任何额外的参数都会被以 `halt`相同的方式来处理: ~~~~ ruby redirect to('/bar'), 303 redirect 'http://google.com', 'wrong place, buddy' ~~~~ 你可以方便的通过 `redirect back`把用户重定向到来自的页面: ~~~~ ruby get '/foo' do "do something" end get '/bar' do do_something redirect back end ~~~~ 为了传递参数给redirect,或者加入query: ~~~~ ruby redirect to('/bar?sum=42') ~~~~ 或者使用session: ~~~~ ruby enable :sessions get '/foo' do session[:secret] = 'foo' redirect to('/bar') end get '/bar' do session[:secret] end ~~~~ ### 缓存控制 正确地设定消息头是恰当的HTTP缓存的基础。 你可以方便的设定 Cache-Control 消息头,像这样: ~~~~ ruby get '/' do cache_control :public "cache it!" end ~~~~ 核心提示: 在前置过滤器中设定缓存. ~~~~ ruby before do cache_control :public, :must_revalidate, :max_age => 60 end ~~~~ 如果你正在用 `expires` 辅助方法设定对应的消息头 `Cache-Control` 会自动设定: ~~~~ ruby before do expires 500, :public, :must_revalidate end ~~~~ 为了合适地使用缓存,你应该考虑使用 `etag` 和 `last_modified`方法。. 推荐在执行繁重任务\*之前\*使用这些helpers, 他们会立刻发送响应,如果客户端在缓存中已经有了当前版本。 ~~~~ ruby get '/article/:id' do @article = Article.find params[:id] last_modified @article.updated_at etag @article.sha1 erb :article end ~~~~ 使用 [weak ETag](http://en.wikipedia.org/wiki/HTTP_ETag#Strong_and_weak_validation) 也是有可能的: ~~~~ ruby etag @article.sha1, :weak ~~~~ 这些辅助方法并不会为你做任何缓存,而是将必要的信息传送给你的缓存 如果你在寻找缓存的快速解决方案,试试 [rack-cache](https://github.com/rtomayko/rack-cache): ~~~~ ruby require "rack/cache" require "sinatra" use Rack::Cache get '/' do cache_control :public, :max_age => 36000 sleep 5 "hello" end ~~~~ ### 发送文件 为了发送文件,你可以使用 `send_file` 辅助方法: ~~~~ ruby get '/' do send_file 'foo.png' end ~~~~ 也可以带一些选项: ~~~~ ruby send_file 'foo.png', :type => :jpg ~~~~ 可用的选项有:
filename
响应中的文件名,默认是真实文件的名字。
last_modified
Last-Modified 消息头的值,默认是文件的mtime(修改时间)。
type
使用的内容类型,如果没有会从文件扩展名猜测。
disposition
用于 Content-Disposition,可能的包括: nil (默认), :attachment:inline
length
Content-Length 的值,默认是文件的大小。
如果Rack处理器支持,Ruby进程除streaming以外的方式会被使用。 如果你使用这个辅助方法, Sinatra会自动处理range请求。 ### 访问请求对象 传入的请求对象可以在请求层(过滤器,路由,错误处理) 通过 `request` 方法被访问: ~~~~ ruby # 在 http://example.com/example 上运行的应用 get '/foo' do request.body # 被客户端设定的请求体(见下) request.scheme # "http" request.script_name # "/example" request.path_info # "/foo" request.port # 80 request.request_method # "GET" request.query_string # "" request.content_length # request.body的长度 request.media_type # request.body的媒体类型 request.host # "example.com" request.get? # true (其他动词也具有类似方法) request.form_data? # false request["SOME_HEADER"] # SOME_HEADER header的值 request.referrer # 客户端的referrer 或者 '/' request.user_agent # user agent (被 :agent 条件使用) request.cookies # 浏览器 cookies 哈希 request.xhr? # 这是否是ajax请求? request.url # "http://example.com/example/foo" request.path # "/example/foo" request.ip # 客户端IP地址 request.secure? # false(如果是ssl则为true) request.forwarded? # true (如果是运行在反向代理之后) request.env # Rack中使用的未处理的env哈希 end ~~~~ 一些选项,例如 `script_name` 或者 `path_info` 也是可写的: ~~~~ ruby before { request.path_info = "/" } get "/" do "all requests end up here" end ~~~~ `request.body` 是一个IO或者StringIO对象: ~~~~ ruby post "/api" do request.body.rewind # 如果已经有人读了它 data = JSON.parse request.body.read "Hello #{data['name']}!" end ~~~~ ### 附件 你可以使用 `attachment` 辅助方法来告诉浏览器响应 应当被写入磁盘而不是在浏览器中显示。 ~~~~ ruby get '/' do attachment "store it!" end ~~~~ 你也可以传递一个文件名: ~~~~ ruby get '/' do attachment "info.txt" "store it!" end ~~~~ ### 查找模板文件 `find_template` 辅助方法被用于在渲染时查找模板文件: ~~~~ ruby find_template settings.views, 'foo', Tilt[:haml] do |file| puts "could be #{file}" end ~~~~ 这并不是很有用。但是在你需要重载这个方法 来实现你自己的查找机制的时候有用。 比如,如果你想支持多于一个视图目录: ~~~~ ruby set :views, ['views', 'templates'] helpers do def find_template(views, name, engine, &block) Array(views).each { |v| super(v, name, engine, &block) } end end ~~~~ 另一个例子是为不同的引擎使用不同的目录: ~~~~ ruby set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views' helpers do def find_template(views, name, engine, &block) _, folder = views.detect { |k,v| engine == Tilt[k] } folder ||= views[:default] super(folder, name, engine, &block) end end ~~~~ 你可以很容易地包装成一个扩展然后与他人分享! 请注意 `find_template` 并不会检查文件真的存在, 而是对任何可能的路径调用给入的代码块。这并不会带来性能问题, 因为 `render` 会在找到文件的时候马上使用 `break` 。 同样的,模板的路径(和内容)会在除development mode以外的场合 被缓存。你应该时刻提醒自己这一点, 如果你真的想写一个非常疯狂的方法。 ## 配置 运行一次,在启动的时候,在任何环境下: ~~~~ ruby configure do # setting one option set :option, 'value' # setting multiple options set :a => 1, :b => 2 # same as `set :option, true` enable :option # same as `set :option, false` disable :option # you can also have dynamic settings with blocks set(:css_dir) { File.join(views, 'css') } end ~~~~ 只当环境 (RACK\_ENV environment 变量) 被设定为 `:production`的时候运行: ~~~~ ruby configure :production do ... end ~~~~ 当环境被设定为 `:production` 或者 `:test`的时候运行: ~~~~ ruby configure :production, :test do ... end ~~~~ 你可以使用 `settings` 获得这些配置: ~~~~ ruby configure do set :foo, 'bar' end get '/' do settings.foo? # => true settings.foo # => 'bar' ... end ~~~~ ### 可选的设置
absolute_redirects

如果被禁用,Sinatra会允许使用相对路径重定向, 但是,Sinatra就不再遵守 RFC 2616标准 (HTTP 1.1), 该标准只允许绝对路径重定向。

如果你的应用运行在一个未恰当设置的反向代理之后, 你需要启用这个选项。注意 url 辅助方法 仍然会生成绝对 URL,除非你传入 false 作为第二参数。

默认禁用。

add_charsets

设定 content_type 辅助方法会 自动加上字符集信息的多媒体类型。

你应该添加而不是覆盖这个选项: settings.add_charsets << "application/foobar"

app_file
主应用文件,用来检测项目的根路径, views和public文件夹和内联模板。
bind
绑定的IP 地址 (默认: 0.0.0.0)。 仅对于内置的服务器有用。
default_encoding
默认编码 (默认为 "utf-8")。
dump_errors
在log中显示错误。
environment
当前环境,默认是 ENV['RACK_ENV'], 或者 "development" 如果不可用。
logging
使用logger
lock

对每一个请求放置一个锁, 只使用进程并发处理请求。

如果你的应用不是线程安全则需启动。 默认禁用。

method_override
使用 _method 魔法以允许在旧的浏览器中在 表单中使用 put/delete 方法
port
监听的端口号。只对内置服务器有用。
prefixed_redirects
是否添加 request.script_name 到 重定向请求,如果没有设定绝对路径。那样的话 redirect '/foo' 会和 redirect to('/foo')起相同作用。默认禁用。
public_folder
public文件夹的位置。
reload_templates
是否每个请求都重新载入模板。 在development mode和 Ruby 1.8.6 中被企业(用来 消除一个Ruby内存泄漏的bug)。
root
项目的根目录。
raise_errors
抛出异常(应用会停下)。
run
如果启用,Sinatra会开启web服务器。 如果使用rackup或其他方式则不要启用。
running
内置的服务器在运行吗? 不要修改这个设置!
server
服务器,或用于内置服务器的列表。 默认是 [‘thin’, ‘mongrel’, ‘webrick’], 顺序表明了 优先级。
sessions
开启基于cookie的sesson。
show_exceptions
在浏览器中显示一个stack trace。
static
Sinatra是否处理静态文件。 当服务器能够处理则禁用。 禁用会增强性能。 默认开启。
views
views 文件夹。
## 错误处理 错误处理在与路由和前置过滤器相同的上下文中运行, 这意味着你可以使用许多好东西,比如 `haml`, `erb`, `halt`,等等。 ### 未找到 当一个 `Sinatra::NotFound` 错误被抛出的时候, 或者响应状态码是404,`not_found` 处理器会被调用: ~~~~ ruby not_found do 'This is nowhere to be found' end ~~~~ ### 错误 `error` 处理器,在任何路由代码块或者过滤器抛出异常的时候会被调用。 异常对象可以通过`sinatra.error` Rack 变量获得: ~~~~ ruby error do 'Sorry there was a nasty error - ' + env['sinatra.error'].name end ~~~~ 自定义错误: ~~~~ ruby error MyCustomError do 'So what happened was...' + env['sinatra.error'].message end ~~~~ 那么,当这个发生的时候: ~~~~ ruby get '/' do raise MyCustomError, 'something bad' end ~~~~ 你会得到: So what happened was... something bad 另一种替代方法是,为一个状态码安装错误处理器: ~~~~ ruby error 403 do 'Access forbidden' end get '/secret' do 403 end ~~~~ 或者一个范围: ~~~~ ruby error 400..510 do 'Boom' end ~~~~ 在运行在development环境下时,Sinatra会安装特殊的 `not_found` 和 `error` 处理器。 ## Rack 中间件 Sinatra 依靠 [Rack](http://rack.rubyforge.org/), 一个面向Ruby web框架的最小标准接口。 Rack的一个最有趣的面向应用开发者的能力是支持“中间件”——坐落在服务器和你的应用之间, 监视 并/或 操作HTTP请求/响应以 提供多样类型的常用功能。 Sinatra 让建立Rack中间件管道异常简单, 通过顶层的 `use` 方法: ~~~~ ruby require 'sinatra' require 'my_custom_middleware' use Rack::Lint use MyCustomMiddleware get '/hello' do 'Hello World' end ~~~~ `use` 的语义和在 [Rack::Builder](http://rack.rubyforge.org/doc/classes/Rack/Builder.html) DSL(在rack文件中最频繁使用)中定义的完全一样。例如,`use` 方法接受 多个/可变 参数,包括代码块: ~~~~ ruby use Rack::Auth::Basic do |username, password| username == 'admin' && password == 'secret' end ~~~~ Rack中分布有多样的标准中间件,针对日志, 调试,URL路由,认证和session处理。 Sinatra会自动使用这里面的大部分组件, 所以你一般不需要显示地 `use` 他们。 ## 测试 Sinatra的测试可以使用任何基于Rack的测试程序库或者框架来编写。 [Rack::Test](http://gitrdoc.com/brynary/rack-test) 是推荐候选: ~~~~ ruby require 'my_sinatra_app' require 'test/unit' require 'rack/test' class MyAppTest < Test::Unit::TestCase include Rack::Test::Methods def app Sinatra::Application end def test_my_default get '/' assert_equal 'Hello World!', last_response.body end def test_with_params get '/meet', :name => 'Frank' assert_equal 'Hello Frank!', last_response.body end def test_with_rack_env get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' assert_equal "You're using Songbird!", last_response.body end end ~~~~ 请注意: 内置的 Sinatra::Test 模块和 Sinatra::TestHarness 类 在 0.9.2 版本已废弃。 ## Sinatra::Base - 中间件,程序库和模块化应用 把你的应用定义在顶层,对于微型应用这会工作得很好, 但是在构建可复用的组件时候会带来客观的不利, 比如构建Rack中间件,Rails metal,带有服务器组件的简单程序库, 或者甚至是Sinatra扩展。顶层的DSL污染了Object命名空间, 并假定了一个微型应用风格的配置 (例如, 单一的应用文件, ./public 和 ./views 目录,日志,异常细节页面,等等)。 这时应该让 Sinatra::Base 走到台前了: ~~~~ ruby require 'sinatra/base' class MyApp < Sinatra::Base set :sessions, true set :foo, 'bar' get '/' do 'Hello world!' end end ~~~~ Sinatra::Base子类可用的方法实际上就是通过顶层 DSL 可用的方法。 大部分顶层应用可以通过两个改变转换成Sinatra::Base组件: - 你的文件应当引入 `sinatra/base` 而不是 `sinatra`; 否则,所有的Sinatra的 DSL 方法将会被引进到 主命名空间。 - 把你的应用的路由,错误处理,过滤器和选项放在 一个Sinatra::Base的子类中。 `+Sinatra::Base+` 是一张白纸。大部分的选项默认是禁用的, 包含内置的服务器。参见 [选项和配置](http://sinatra.github.com/configuration.html) 查看可用选项的具体细节和他们的行为。 ### 模块化 vs. 传统的方式 与通常的认识相反,传统的方式没有任何错误。 如果它适合你的应用,你不需要转换到模块化的应用。 和模块化方式相比只有两个缺点: - 你对每个Ruby进程只能定义一个Sinatra应用,如果你需要更多, 切换到模块化方式。 - 传统方式使用代理方法污染了 Object 。如果你打算 把你的应用封装进一个 library/gem,转换到模块化方式。 没有任何原因阻止你混合模块化和传统方式。 如果从一种转换到另一种,你需要注意settings中的 一些微小的不同: Setting Classic Modular app_file file loading sinatra nil run $0 == app_file false logging true false method_override true false inline_templates true false ### 运行一个模块化应用 有两种方式运行一个模块化应用,使用 `run!`来运行: ~~~~ ruby # my_app.rb require 'sinatra/base' class MyApp < Sinatra::Base # ... app code here ... # start the server if ruby file executed directly run! if app_file == $0 end ~~~~ 运行: ruby my_app.rb 或者使用一个 `config.ru`,允许你使用任何Rack处理器: ~~~~ ruby # config.ru require './my_app' run MyApp ~~~~ 运行: rackup -p 4567 ### 使用config.ru运行传统方式的应用 编写你的应用: ~~~~ ruby # app.rb require 'sinatra' get '/' do 'Hello world!' end ~~~~ 加入相应的 `config.ru`: ~~~~ ruby require './app' run Sinatra::Application ~~~~ ### 什么时候用 config.ru? 以下情况你可能需要使用 `config.ru`: - 你要使用不同的 Rack 处理器部署 (Passenger, Unicorn, Heroku, …). - 你想使用一个或者多个 `Sinatra::Base`的子类. - 你只想把Sinatra当作中间件使用,而不是端点。 **你并不需要切换到`config.ru`仅仅因为你切换到模块化方式, 你同样不需要切换到模块化方式, 仅仅因为要运行 `config.ru`.** ### 把Sinatra当成中间件来使用 不仅Sinatra有能力使用其他的Rack中间件,任何Sinatra 应用程序都可以反过来自身被当作中间件,被加在任何Rack端点前面。 这个端点可以是任何Sinatra应用,或者任何基于Rack的应用程序 (Rails/Ramaze/Camping/…)。 ~~~~ ruby require 'sinatra/base' class LoginScreen < Sinatra::Base enable :sessions get('/login') { haml :login } post('/login') do if params[:name] = 'admin' and params[:password] = 'admin' session['user_name'] = params[:name] else redirect '/login' end end end class MyApp < Sinatra::Base # 在前置过滤器前运行中间件 use LoginScreen before do unless session['user_name'] halt "Access denied, please login." end end get('/') { "Hello #{session['user_name']}." } end ~~~~ ## 变量域和绑定 当前所在的变量域决定了哪些方法和变量是可用的。 ### 应用/类 变量域 每个Sinatra应用相当与Sinatra::Base的一个子类。 如果你在使用顶层DSL(`require 'sinatra'`),那么这个类就是 Sinatra::Application,或者这个类就是你显式创建的子类。 在类层面,你具有的方法类似于 \`get\` 或者 \`before\`,但是你不能访问 \`request\` 对象或者 \`session\`, 因为对于所有的请求, 只有单一的应用类。 通过 \`set\` 创建的选项是类层面的方法: ~~~~ ruby class MyApp < Sinatra::Base # 嘿,我在应用变量域! set :foo, 42 foo # => 42 get '/foo' do # 嘿,我不再处于应用变量域了! end end ~~~~ 在下列情况下你将拥有应用变量域的绑定: - 在应用类中 - 在扩展中定义的方法 - 传递给 \`helpers\` 的代码块 - 用作\`set\`值的过程/代码块 你可以访问变量域对象(就是应用类)就像这样: - 通过传递给代码块的对象 (`configure { |c| ... }`) - 在请求变量域中使用\`settings\` ### 请求/实例 变量域 对于每个进入的请求,一个新的应用类的实例会被创建 所有的处理器代码块在该变量域被运行。在这个变量域中, 你可以访问 \`request\` 和 \`session\` 对象,或者调用渲染方法比如 \`erb\` 或者 \`haml\`。你可以在请求变量域当中通过\`settings\`辅助方法 访问应用变量域: ~~~~ ruby class MyApp < Sinatra::Base # 嘿,我在应用变量域! get '/define_route/:name' do # 针对 '/define_route/:name' 的请求变量域 @value = 42 settings.get("/#{params[:name]}") do # 针对 "/#{params[:name]}" 的请求变量域 @value # => nil (并不是相同的请求) end "Route defined!" end end ~~~~ 在以下情况将获得请求变量域: - get/head/post/put/delete 代码块 - 前置/后置 过滤器 - 辅助方法 - 模板/视图 ### 代理变量域 代理变量域只是把方法转送到类变量域。可是, 他并非表现得100%类似于类变量域, 因为你并不能获得类的绑定: 只有显式地标记为供代理使用的方法才是可用的, 而且你不能和类变量域共享变量/状态。(解释:你有了一个不同的 \`self\`)。 你可以显式地增加方法代理,通过调用 `Sinatra::Delegator.delegate :method_name`。 在以下情况将获得代理变量域: - 顶层的绑定,如果你做过 `require "sinatra"` - 在扩展了 \`Sinatra::Delegator\` mixin的对象 自己在这里看一下代码: [Sinatra::Delegator mixin](http://github.com/sinatra/sinatra/blob/ceac46f0bc129a6e994a06100aa854f606fe5992/lib/sinatra/base.rb#L1128) 已经 [被包含进了主命名空间](http://github.com/sinatra/sinatra/blob/ceac46f0bc129a6e994a06100aa854f606fe5992/lib/sinatra/main.rb#L28)。 ## 命令行 Sinatra 应用可以被直接运行: ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER] 选项是: -h # help -p # 设定端口 (默认是 4567) -o # 设定主机名 (默认是 0.0.0.0) -e # 设定环境 (默认是 development) -s # 限定 rack 服务器/处理器 (默认是 thin) -x # 打开互斥锁 (默认是 off) ## 必要条件 推荐在 Ruby 1.8.7, 1.9.2, JRuby 或者 Rubinius 上安装Sinatra。 下面的Ruby版本是官方支持的:
Ruby 1.8.6
不推荐在1.8.6上安装Sinatra, 但是直到Sinatra 1.3.0发布才会放弃对它的支持。 RDoc 和 CoffeScript模板不被这个Ruby版本支持。 1.8.6在它的Hash实现中包含一个内存泄漏问题, 该问题会被1.1.1版本之前的Sinatra引发。 当前版本使用性能下降的代价排除了这个问题。你需要把Rack降级到1.1.x, 因为Rack \>= 1.2不再支持1.8.6。
Ruby 1.8.7
1.8.7 被完全支持,但是,如果没有特别原因, 我们推荐你升级到 1.9.2 或者切换到 JRuby 或者 Rubinius.
Ruby 1.9.2
1.9.2 被支持而且推荐。注意 Radius 和 Markaby 模板并不和1.9兼容。不要使用 1.9.2p0, 它被已知会产生 segmentation faults.
Rubinius
Rubinius 被官方支持 (Rubinius \>= 1.2.2), 除了Textile模板。
JRuby
JRuby 被官方支持 (JRuby \>= 1.5.6)。 目前未知和第三方模板库有关的问题, 但是,如果你选择了JRuby,请查看一下JRuby rack 处理器, 因为 Thin web 服务器还没有在JRuby上获得支持。
我们也会时刻关注新的Ruby版本。 下面的 Ruby 实现没有被官方支持, 但是已知可以运行 Sinatra: - JRuby 和 Rubinius 老版本 - MacRuby - Maglev - IronRuby - Ruby 1.9.0 and 1.9.1 不被官方支持的意思是,如果在不被支持的平台上有运行错误, 我们假定不是我们的问题,而是平台的问题。 Sinatra应该会运行在任何支持上述Ruby实现的操作系统。 ## 紧追前沿 如果你喜欢使用 Sinatra 的最新鲜的代码,请放心的使用 master 分支来运行你的程序,它会非常的稳定。 cd myapp git clone git://github.com/sinatra/sinatra.git ruby -Isinatra/lib myapp.rb 我们也会不定期的发布预发布gems,所以你也可以运行 gem install sinatra --pre 来获得最新的特性。 ### 通过Bundler 如果你想使用最新的Sinatra运行你的应用,通过 [Bundler](http://gembundler.com/) 是推荐的方式。 首先,安装bundler,如果你还没有安装: gem install bundler 然后,在你的项目目录下,创建一个 `Gemfile`: ~~~~ ruby source :rubygems gem 'sinatra', :git => "git://github.com/sinatra/sinatra.git" # 其他的依赖关系 gem 'haml' # 举例,如果你想用haml gem 'activerecord', '~> 3.0' # 也许你还需要 ActiveRecord 3.x ~~~~ 请注意在这里你需要列出你的应用的所有依赖关系。 Sinatra的直接依赖关系 (Rack and Tilt) 将会, 自动被Bundler获取和添加。 现在你可以像这样运行你的应用: bundle exec ruby myapp.rb ### 使用自己的 创建一个本地克隆并通过 `sinatra/lib` 目录运行你的应用, 通过 `$LOAD_PATH`: cd myapp git clone git://github.com/sinatra/sinatra.git ruby -Isinatra/lib myapp.rb 为了在未来更新 Sinatra 源代码: cd myapp/sinatra git pull ### 全局安装 你可以自行编译 gem : git clone git://github.com/sinatra/sinatra.git cd sinatra rake sinatra.gemspec rake install 如果你以root身份安装 gems,最后一步应该是 sudo rake install ## 更多 - [项目主页(英文)](http://www.sinatrarb.com/) - 更多的文档, 新闻,和其他资源的链接。 - [贡献](http://www.sinatrarb.com/contributing) - 找到了一个bug? 需要帮助?有了一个 patch? - [问题追踪](http://github.com/sinatra/sinatra/issues) - [Twitter](http://twitter.com/sinatra) - [邮件列表](http://groups.google.com/group/sinatrarb/topics) - [IRC: \#sinatra](irc://chat.freenode.net/#sinatra) on [freenode.net](http://freenode.net) sinatra-1.4.3/metadata.yml0000644000004100000410000001413712161612727015526 0ustar www-datawww-data--- !ruby/object:Gem::Specification name: sinatra version: !ruby/object:Gem::Version version: 1.4.3 platform: ruby authors: - Blake Mizerany - Ryan Tomayko - Simon Rozet - Konstantin Haase autorequire: bindir: bin cert_chain: [] date: 2013-06-07 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: rack requirement: !ruby/object:Gem::Requirement requirements: - - ~> - !ruby/object:Gem::Version version: '1.4' type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ~> - !ruby/object:Gem::Version version: '1.4' - !ruby/object:Gem::Dependency name: tilt requirement: !ruby/object:Gem::Requirement requirements: - - ~> - !ruby/object:Gem::Version version: '1.3' - - '>=' - !ruby/object:Gem::Version version: 1.3.4 type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ~> - !ruby/object:Gem::Version version: '1.3' - - '>=' - !ruby/object:Gem::Version version: 1.3.4 - !ruby/object:Gem::Dependency name: rack-protection requirement: !ruby/object:Gem::Requirement requirements: - - ~> - !ruby/object:Gem::Version version: '1.4' type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ~> - !ruby/object:Gem::Version version: '1.4' description: Sinatra is a DSL for quickly creating web applications in Ruby with minimal effort. email: sinatrarb@googlegroups.com executables: [] extensions: [] extra_rdoc_files: - README.de.md - README.es.md - README.fr.md - README.hu.md - README.jp.md - README.ko.md - README.md - README.pt-br.md - README.pt-pt.md - README.ru.md - README.zh.md - LICENSE files: - .yardopts - AUTHORS - CHANGES - Gemfile - LICENSE - README.de.md - README.es.md - README.fr.md - README.hu.md - README.jp.md - README.ko.md - README.md - README.pt-br.md - README.pt-pt.md - README.ru.md - README.zh.md - Rakefile - examples/chat.rb - examples/simple.rb - examples/stream.ru - lib/sinatra.rb - lib/sinatra/base.rb - lib/sinatra/images/404.png - lib/sinatra/images/500.png - lib/sinatra/main.rb - lib/sinatra/showexceptions.rb - lib/sinatra/version.rb - sinatra.gemspec - test/base_test.rb - test/builder_test.rb - test/coffee_test.rb - test/compile_test.rb - test/contest.rb - test/creole_test.rb - test/delegator_test.rb - test/encoding_test.rb - test/erb_test.rb - test/extensions_test.rb - test/filter_test.rb - test/haml_test.rb - test/helper.rb - test/helpers_test.rb - test/integration/app.rb - test/integration_helper.rb - test/integration_test.rb - test/less_test.rb - test/liquid_test.rb - test/mapped_error_test.rb - test/markaby_test.rb - test/markdown_test.rb - test/middleware_test.rb - test/nokogiri_test.rb - test/public/favicon.ico - test/rabl_test.rb - test/rack_test.rb - test/radius_test.rb - test/rdoc_test.rb - test/readme_test.rb - test/request_test.rb - test/response_test.rb - test/result_test.rb - test/route_added_hook_test.rb - test/routing_test.rb - test/sass_test.rb - test/scss_test.rb - test/server_test.rb - test/settings_test.rb - test/sinatra_test.rb - test/slim_test.rb - test/static_test.rb - test/streaming_test.rb - test/stylus_test.rb - test/templates_test.rb - test/textile_test.rb - test/views/a/in_a.str - test/views/ascii.erb - test/views/b/in_b.str - test/views/calc.html.erb - test/views/error.builder - test/views/error.erb - test/views/error.haml - test/views/error.sass - test/views/explicitly_nested.str - test/views/foo/hello.test - test/views/hello.builder - test/views/hello.coffee - test/views/hello.creole - test/views/hello.erb - test/views/hello.haml - test/views/hello.less - test/views/hello.liquid - test/views/hello.mab - test/views/hello.md - test/views/hello.nokogiri - test/views/hello.rabl - test/views/hello.radius - test/views/hello.rdoc - test/views/hello.sass - test/views/hello.scss - test/views/hello.slim - test/views/hello.str - test/views/hello.styl - test/views/hello.test - test/views/hello.textile - test/views/hello.wlang - test/views/hello.yajl - test/views/layout2.builder - test/views/layout2.erb - test/views/layout2.haml - test/views/layout2.liquid - test/views/layout2.mab - test/views/layout2.nokogiri - test/views/layout2.rabl - test/views/layout2.radius - test/views/layout2.slim - test/views/layout2.str - test/views/layout2.test - test/views/layout2.wlang - test/views/nested.str - test/views/utf8.erb - test/wlang_test.rb - test/yajl_test.rb homepage: http://www.sinatrarb.com/ licenses: [] metadata: {} post_install_message: rdoc_options: - --line-numbers - --inline-source - --title - Sinatra - --main - README.rdoc - --encoding=UTF-8 require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - '>=' - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: rubygems_version: 2.0.2 signing_key: specification_version: 4 summary: Classy web-development dressed in a DSL test_files: - test/base_test.rb - test/builder_test.rb - test/coffee_test.rb - test/compile_test.rb - test/creole_test.rb - test/delegator_test.rb - test/encoding_test.rb - test/erb_test.rb - test/extensions_test.rb - test/filter_test.rb - test/haml_test.rb - test/helpers_test.rb - test/integration_test.rb - test/less_test.rb - test/liquid_test.rb - test/mapped_error_test.rb - test/markaby_test.rb - test/markdown_test.rb - test/middleware_test.rb - test/nokogiri_test.rb - test/rabl_test.rb - test/rack_test.rb - test/radius_test.rb - test/rdoc_test.rb - test/readme_test.rb - test/request_test.rb - test/response_test.rb - test/result_test.rb - test/route_added_hook_test.rb - test/routing_test.rb - test/sass_test.rb - test/scss_test.rb - test/server_test.rb - test/settings_test.rb - test/sinatra_test.rb - test/slim_test.rb - test/static_test.rb - test/streaming_test.rb - test/stylus_test.rb - test/templates_test.rb - test/textile_test.rb - test/wlang_test.rb - test/yajl_test.rb has_rdoc: sinatra-1.4.3/.yardopts0000644000004100000410000000014212161612727015060 0ustar www-datawww-data--readme README.rdoc --title 'Sinatra API Documentation' --charset utf-8 'lib/**/*.rb' - '*.rdoc' sinatra-1.4.3/sinatra.gemspec0000644000004100000410000000173612161612727016232 0ustar www-datawww-data$LOAD_PATH.unshift File.expand_path('../lib', __FILE__) require 'sinatra/version' Gem::Specification.new 'sinatra', Sinatra::VERSION do |s| 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/" s.files = `git ls-files`.split("\n") - %w[.gitignore .travis.yml] s.test_files = s.files.select { |p| p =~ /^test\/.*_test.rb/ } s.extra_rdoc_files = s.files.select { |p| p =~ /^README/ } << 'LICENSE' s.rdoc_options = %w[--line-numbers --inline-source --title Sinatra --main README.rdoc --encoding=UTF-8] s.add_dependency 'rack', '~> 1.4' s.add_dependency 'tilt', '~> 1.3', '>= 1.3.4' s.add_dependency 'rack-protection', '~> 1.4' end sinatra-1.4.3/Gemfile0000644000004100000410000000461712161612727014520 0ustar www-datawww-data# Why use bundler? # Well, not all development dependencies install on all rubies. Moreover, `gem # install sinatra --development` doesn't work, as it will also try to install # development dependencies of our dependencies, and those are not conflict free. # So, here we are, `bundle install`. # # If you have issues with a gem: `bundle install --without-coffee-script`. RUBY_ENGINE = 'ruby' unless defined? RUBY_ENGINE source 'https://rubygems.org' unless ENV['QUICK'] gemspec gem 'rake' gem 'rack-test', '>= 0.5.6' gem 'ci_reporter', :group => :ci # Allows stuff like `tilt=1.2.2 bundle install` or `tilt=master ...`. # Used by the CI. github = "git://github.com/%s.git" repos = {'tilt' => github % "rtomayko/tilt", 'rack' => github % "rack/rack"} %w[tilt rack].each do |lib| dep = case ENV[lib] when 'stable', nil then nil when /(\d+\.)+\d+/ then "~> " + ENV[lib].sub("#{lib}-", '') else {:git => repos[lib], :branch => dep} end gem lib, dep end gem 'haml', '>= 3.0' gem 'sass' if RUBY_VERSION < "2.0" gem 'builder' gem 'erubis' gem 'slim', '~> 1.0' gem 'temple', '!= 0.3.3' gem 'coffee-script', '>= 2.0' gem 'rdoc', RUBY_VERSION < '1.9' ? '~> 3.12' : '>= 4.0' gem 'kramdown' gem 'maruku' gem 'creole' gem 'markaby' gem 'radius' gem 'rabl' unless RUBY_ENGINE =~ /jruby|maglev/ gem 'wlang', '>= 2.0.1' unless RUBY_ENGINE =~ /jruby|rbx/ gem 'therubyracer' unless RUBY_ENGINE =~ /jruby|rbx/ gem 'redcarpet' unless RUBY_ENGINE == 'jruby' gem 'bluecloth' unless RUBY_ENGINE == 'jruby' if RUBY_ENGINE != 'rbx' or RUBY_VERSION < '1.9' gem 'liquid' gem 'stylus' end if RUBY_ENGINE == 'jruby' gem 'nokogiri', '!= 1.5.0' gem 'jruby-openssl' gem 'trinidad' else gem 'yajl-ruby' gem 'nokogiri' gem 'thin' end if RUBY_ENGINE == "ruby" and RUBY_VERSION > '1.9' gem 'less', '~> 2.0' end if RUBY_ENGINE != 'jruby' or not ENV['TRAVIS'] # C extensions gem 'rdiscount' if RUBY_VERSION != '1.9.2' platforms(:ruby_18) do gem 'redcarpet' gem 'mongrel' end gem 'RedCloth' unless RUBY_ENGINE == "macruby" gem 'puma' end gem 'net-http-server' unless RUBY_VERSION == '1.8.7' || RUBY_ENGINE =~ /jruby|rbx/ platforms :ruby_18, :jruby do gem 'json' unless RUBY_VERSION > '1.9' # is there a jruby but 1.8 only selector? end platforms :mri_18 do # bundler platforms are broken next if RUBY_ENGINE != 'ruby' or RUBY_VERSION > "1.8" gem 'rcov' end sinatra-1.4.3/AUTHORS0000644000004100000410000000643212161612727014272 0ustar www-datawww-dataSinatra was designed and developed by Blake Mizerany (bmizerany) in California. Sinatra would not have been possible without strong company backing. In the past, financial and emotional support have been provided mainly by [Heroku](http://heroku.com), [GitHub](http://github.com) and [Engine Yard](http://www.engineyard.com/), and is now taken care of by [Travis CI](http://travis-ci.com/). Special thanks to the following extraordinary individuals, who-out which Sinatra would not be possible: * Ryan Tomayko (rtomayko) for constantly fixing whitespace errors 60d5006 * Ezra Zygmuntowicz (ezmobius) for initial help and letting Blake steal some of merbs internal code. * Ari Lerner (http://xnot.org/) for his evangelism, spirit, and gumption that got Sinatra recognized from Day 1. * Christopher Schneid (cschneid) for The Book, the blog (gittr.com), irclogger.com, and a bunch of useful patches. * Markus Prinz (cypher) for patches over the years, caring about the README, and hanging in there when times were rough. * Simon Rozet (sr) for a ton of doc patches, HAML options, and all that advocacy stuff he's going to do for 1.0. * Erik Kastner (kastner) for fixing `MIME_TYPES` under Rack 0.5. * Ben Bleything (bleything) for caring about HTTP status codes and doc fixes. * Igal Koshevoy (igal) for root path detection under Thin/Passenger. * Jon Crosby (jcrosby) for coffee breaks, doc fixes, and just because, man. * Karel Minarik (karmi) for screaming until the website came back up. * Jeremy Evans (jeremyevans) for unbreaking optional path params (twice!) * The GitHub guys for stealing Blake's table. * Nickolas Means (nmeans) for Sass template support. * Victor Hugo Borja (vic) for splat'n routes specs and doco. * Avdi Grimm (avdi) for basic RSpec support. * Jack Danger Canty for a more accurate root directory and for making me watch [this](http://www.youtube.com/watch?v=ueaHLHgskkw) just now. * Mathew Walker for making escaped paths work with static files. * Millions of Us for having the problem that led to Sinatra's conception. * Songbird for the problems that helped Sinatra's future become realized. * Rick Olson (technoweenie) for the killer plug at RailsConf '08. * Steven Garcia for the amazing custom artwork you see on 404's and 500's * Pat Nakajima (nakajima) for fixing non-nested params in nested params Hash's. * Konstantin Haase for his hard work and ongoing commitment to improving Sinatra, for 1.1.0, 1.2.0 and beyond.. * Zachary Scott for adding Konstantin to the AUTHORS file. He also did help writing the book, but mainly for adding Konstantin. * Gabriel Andretta for having people wonder whether our documentation is actually in English or in Spanish. * Vasily Polovnyov, Nickolay Schwarz, Luciano Sousa, Wu Jiang, Mickael Riga, Bernhard Essl, Janos Hardi, Kouhei Yanagita and "burningTyger" for willingly translating whatever ends up in the README. * [Wordy](http://www.wordy.com/) for proofreading our README. 73e137d * cactus for digging through code and specs, multiple times. * Nicolás Sanguinetti (foca) for strong demand of karma and shaping helpers/register. and last but not least: * Frank Sinatra (chairman of the board) for having so much class he deserves a web-framework named after him. For a complete list of all contributors to Sinatra itself, run `rake authors`. sinatra-1.4.3/README.ru.md0000644000004100000410000027601412161612727015133 0ustar www-datawww-data# Sinatra *Внимание: Этот документ является переводом английской версии и может быть устаревшим* Sinatra — это предметно-ориентированный каркас ([DSL](http://ru.wikipedia.org/wiki/Предметно-ориентированный_язык_программирования)) для быстрого создания функциональных веб-приложений на Ruby с минимумом усилий: ```ruby # myapp.rb require 'sinatra' get '/' do 'Hello world!' end ``` Установите gem: ``` gem install sinatra ``` и запустите приложение с помощью: ``` ruby myapp.rb ``` Оцените результат: http://localhost:4567 Рекомендуется также установить Thin, сделать это можно командой: `gem install thin`. Thin — это более производительный и функциональный сервер для разработки приложений на Sinatra. ## Маршруты В Sinatra маршрут — это пара: <HTTP метод> и <шаблон URL>. Каждый маршрут связан с блоком кода: ```ruby get '/' do # .. что-то показать .. end post '/' do # .. что-то создать .. end put '/' do # .. что-то заменить .. end patch '/' do # .. что-то изменить .. end delete '/' do # .. что-то удалить .. end options '/' do # .. что-то ответить .. end ``` Маршруты сверяются с запросом в порядке очередности их записи в файле приложения. Первый же совпавший с запросом маршрут и будет вызван. Шаблоны маршрутов могут включать в себя именованные параметры, доступные в xэше `params`: ```ruby get '/hello/:name' do # соответствует "GET /hello/foo" и "GET /hello/bar", # где params[:name] 'foo' или 'bar' "Hello #{params[:name]}!" end ``` Также можно использовать именованные параметры в качестве переменных блока: ```ruby get '/hello/:name' do |n| "Hello #{n}!" end ``` Шаблоны маршрутов также могут включать в себя splat (или '*' маску, обозначающую любой символ) параметры, доступные в массиве `params[:splat]`: ```ruby get '/say/*/to/*' do # соответствует /say/hello/to/world params[:splat] # => ["hello", "world"] end get '/download/*.*' do # соответствует /download/path/to/file.xml params[:splat] # => ["path/to/file", "xml"] end ``` Или с параметрами блока: ```ruby get '/download/*.*' do |path, ext| [path, ext] # => ["path/to/file", "xml"] end ``` Регулярные выражения в качестве шаблонов маршрутов: ```ruby get %r{/hello/([\w]+)} do "Hello, #{params[:captures].first}!" end ``` Или с параметром блока: ```ruby get %r{/hello/([\w]+)} do |c| "Hello, #{c}!" end ``` Шаблоны маршрутов могут иметь необязательные параметры: ```ruby get '/posts.?:format?' do # соответствует "GET /posts", "GET /posts.json", "GET /posts.xml" и т.д. end ``` Кстати, если вы не отключите защиту от обратного пути в директориях (path traversal, см. ниже), путь запроса может быть изменен до начала поиска подходящего маршрута. ### Условия Маршруты могут включать различные условия совпадений, например, клиентское приложение (user agent): ```ruby get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do "You're using Songbird version #{params[:agent][0]}" end get '/foo' do # соответствует не-songbird браузерам end ``` Другими доступными условиями являются `host_name` и `provides`: ```ruby get '/', :host_name => /^admin\./ do "Admin Area, Access denied!" end get '/', :provides => 'html' do haml :index end get '/', :provides => ['rss', 'atom', 'xml'] do builder :feed end ``` Вы можете задать собственные условия: ```ruby set(:probability) { |value| condition { rand <= value } } get '/win_a_car', :probability => 0.1 do "You won!" end get '/win_a_car' do "Sorry, you lost." end ``` Для условия, которое принимает несколько параметров, используйте звездочку: ```ruby set(:auth) do |*roles| # <- обратите внимание на звездочку 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 ``` ### Возвращаемые значения Возвращаемое значение блока маршрута ограничивается телом ответа, которое будет передано HTTP клиенту, или следующей "прослойкой" (middleware) в Rack стеке. Чаще всего это строка, как в примерах выше. Но также приемлемы и другие значения. Вы можете вернуть любой объект, который будет либо корректным Rack ответом, объектом Rack body, либо кодом состояния HTTP: * массив с тремя переменными: `[код (Fixnum), заголовки (Hash), тело ответа (должно отвечать на #each)]`; * массив с двумя переменными: `[код (Fixnum), тело ответа (должно отвечать на #each)]`; * объект, отвечающий на `#each`, который передает только строковые типы данных в этот блок; * Fixnum, представляющий код состояния HTTP. Таким образом, легко можно реализовать, например, поточный пример: ```ruby class Stream def each 100.times { |i| yield "#{i}\n" } end end get('/') { Stream.new } ``` Вы также можете использовать метод `stream` (описываемый ниже), чтобы уменьшить количество дублируемого кода и держать логику стриминга прямо в маршруте. ### Собственные детекторы совпадений для маршрутов Как показано выше, Sinatra поставляется со встроенной поддержкой строк и регулярных выражений в качестве шаблонов URL. Но и это еще не все. Вы можете легко определить свои собственные детекторы совпадений (matchers) для маршрутов: ```ruby class AllButPattern Match = Struct.new(:captures) def initialize(except) @except = except @captures = Match.new([]) end def match(str) @captures unless @except === str end end def all_but(pattern) AllButPattern.new(pattern) end get all_but("/index") do # ... end ``` Заметьте, что предыдущий пример, возможно, чересчур усложнен, потому что он может быть реализован так: ```ruby get // do pass if request.path_info == "/index" # ... end ``` Или с использованием негативного просмотра вперед: ```ruby get %r{^(?!/index$)} do # ... end ``` ## Статические файлы Статические файлы отдаются из `./public` директории. Вы можете указать другое место, используя опцию `:public_folder`: ```ruby set :public_folder, File.dirname(__FILE__) + '/static' ``` Учтите, что имя директории со статическими файлами не включено в URL. Например, файл `./public/css/style.css` будет доступен как `http://example.com/css/style.css`. Используйте опцию `:static_cache_control` (см. ниже), чтобы добавить заголовок `Cache-Control`. ## Представления / Шаблоны Каждый шаблонизатор представлен своим собственным методом. Эти методы попросту возвращают строку: ```ruby get '/' do erb :index end ``` Отобразит `views/index.erb`. Вместо имени шаблона вы так же можете передавать непосредственно само содержимое шаблона: ```ruby get '/' do code = "<%= Time.now %>" erb code end ``` Эти методы принимают второй аргумент, хеш с опциями: ```ruby get '/' do erb :index, :layout => :post end ``` Отобразит `views/index.erb`, вложенным в `views/post.erb` (по умолчанию: `views/layout.erb`, если существует). Любые опции, не понимаемые Sinatra, будут переданы в шаблонизатор: ```ruby get '/' do haml :index, :format => :html5 end ``` Вы также можете задавать опции для шаблонизаторов в общем: ```ruby set :haml, :format => :html5 get '/' do haml :index end ``` Опции, переданные в метод, переопределяют опции, заданные с помощью `set`. Доступные опции:
locals
Список локальных переменных, передаваемых в документ. Например: erb "<%= foo %>", :locals => {:foo => "bar"}
default_encoding
Кодировка, которую следует использовать, если не удалось определить оригинальную. По умолчанию: settings.default_encoding.
views
Директория с шаблонами. По умолчанию: settings.views.
layout
Использовать или нет лэйаут (true или false). Если же значение Symbol, то указывает, какой шаблон использовать в качестве лэйаута. Например: erb :index, :layout => !request.xhr?
content_type
Content-Type отображенного шаблона. По умолчанию: задается шаблонизатором.
scope
Область видимости, в которой рендерятся шаблоны. По умолчанию: экземпляр приложения. Если вы измените эту опцию, то переменные экземпляра и методы-помощники станут недоступными в ваших шаблонах.
layout_engine
Шаблонизатор, который следует использовать для отображения лэйаута. Полезная опция для шаблонизаторов, в которых нет никакой поддержки лэйаутов. По умолчанию: тот же шаблонизатор, что используется и для самого шаблона. Пример: set :rdoc, :layout_engine => :erb
По умолчанию считается, что шаблоны находятся в директории `./views`. Чтобы использовать другую директорию с шаблонами: ```ruby set :views, settings.root + '/templates' ``` Важное замечание: вы всегда должны ссылаться на шаблоны с помощью символов (Symbol), даже когда они в поддиректории (в этом случае используйте `:'subdir/template'`). Вы должны использовать символы, потому что иначе шаблонизаторы попросту отображают любые строки, переданные им. ### Буквальные шаблоны ```ruby get '/' do haml '%div.title Hello World' end ``` Отобразит шаблон, переданный строкой. ### Доступные шаблонизаторы Некоторые языки шаблонов имеют несколько реализаций. Чтобы указать, какую реализацию использовать, вам следует просто подключить нужную библиотеку: ```ruby require 'rdiscount' # или require 'bluecloth' get('/') { markdown :index } ``` #### Haml шаблоны
Зависимости haml
Расширения файлов .haml
Пример haml :index, :format => :html5
#### Erb шаблоны
Зависимости erubis или erb (включен в Ruby)
Расширения файлов .erb, .rhtml or .erubis (только Erubis)
Пример erb :index
#### Builder шаблоны
Зависимости builder
Расширения файлов .builder
Пример builder { |xml| xml.em "hi" }
Блок также используется и для встроенных шаблонов (см. пример). #### Nokogiri шаблоны
Зависимости nokogiri
Расширения файлов .nokogiri
Пример nokogiri { |xml| xml.em "hi" }
Блок также используется и для встроенных шаблонов (см. пример). #### Sass шаблоны
Зависимости sass
Расширения файлов .sass
Пример sass :stylesheet, :style => :expanded
#### SCSS шаблоны
Зависимости sass
Расширения файлов .scss
Пример scss :stylesheet, :style => :expanded
#### Less шаблоны
Зависимости less
Расширения файлов .less
Пример less :stylesheet
#### Liquid шаблоны
Зависимости liquid
Расширения файлов .liquid
Пример liquid :index, :locals => { :key => 'value' }
Так как в Liquid шаблонах невозможно вызывать методы из Ruby (кроме `yield`), то вы почти всегда будете передавать в шаблон локальные переменные. #### Markdown шаблоны
Зависимости Любая из библиотек: RDiscount, RedCarpet, BlueCloth, kramdown, maruku
Расширения файлов .markdown, .mkd and .md
Пример markdown :index, :layout_engine => :erb
В Markdown невозможно вызывать методы или передавать локальные переменные. Следовательно, вам, скорее всего, придется использовать этот шаблон совместно с другим шаблонизатором: ```ruby erb :overview, :locals => { :text => markdown(:introduction) } ``` Заметьте, что вы можете вызывать метод `markdown` из других шаблонов: ```ruby %h1 Hello From Haml! %p= markdown(:greetings) ``` Вы не можете вызывать Ruby из Markdown, соответственно, вы не можете использовать лэйауты на Markdown. Тем не менее, есть возможность использовать один шаблонизатор для отображения шаблона, а другой для лэйаута с помощью опции `:layout_engine`. #### Textile шаблоны
Зависимости RedCloth
Расширения файлов .textile
Пример textile :index, :layout_engine => :erb
В Textile невозможно вызывать методы или передавать локальные переменные. Следовательно, вам, скорее всего, придется использовать этот шаблон совместно с другим шаблонизатором: ```ruby erb :overview, :locals => { :text => textile(:introduction) } ``` Заметьте, что вы можете вызывать метод `textile` из других шаблонов: ```ruby %h1 Hello From Haml! %p= textile(:greetings) ``` Вы не можете вызывать Ruby из Textile, соответственно, вы не можете использовать лэйауты на Textile. Тем не менее, есть возможность использовать один шаблонизатор для отображения шаблона, а другой для лэйаута с помощью опции `:layout_engine`. #### RDoc шаблоны
Зависимости RDoc
Расширения файлов .rdoc
Пример rdoc :README, :layout_engine => :erb
В RDoc невозможно вызывать методы или передавать локальные переменные. Следовательно, вам, скорее всего, придется использовать этот шаблон совместно с другим шаблонизатором: ```ruby erb :overview, :locals => { :text => rdoc(:introduction) } ``` Заметьте, что вы можете вызывать метод `rdoc` из других шаблонов: ```ruby %h1 Hello From Haml! %p= rdoc(:greetings) ``` Вы не можете вызывать Ruby из RDoc, соответственно, вы не можете использовать лэйауты на RDoc. Тем не менее, есть возможность использовать один шаблонизатор для отображения шаблона, а другой для лэйаута с помощью опции `:layout_engine`. #### Radius шаблоны
Зависимости Radius
Расширения файлов .radius
Пример radius :index, :locals => { :key => 'value' }
Так как в Radius шаблонах невозможно вызывать методы из Ruby напрямую, то вы почти всегда будете передавать в шаблон локальные переменные. #### Markaby шаблоны
Зависимости Markaby
Расширения файлов .mab
Пример markaby { h1 "Welcome!" }
Блок также используется и для встроенных шаблонов (см. пример). #### RABL шаблоны
Зависимости Rabl
Расширения файлов .rabl
Пример rabl :index
#### Slim шаблоны
Зависимости Slim Lang
Расширения файлов .slim
Пример slim :index
#### Creole шаблоны
Зависимости Creole
Расширения файлов .creole
Пример creole :wiki, :layout_engine => :erb
В Creole невозможно вызывать методы или передавать локальные переменные. Следовательно, вам, скорее всего, придется использовать этот шаблон совместно с другим шаблонизатором: ```ruby erb :overview, :locals => { :text => creole(:introduction) } ``` Заметьте, что вы можете вызывать метод `creole` из других шаблонов: ```ruby %h1 Hello From Haml! %p= creole(:greetings) ``` Вы не можете вызывать Ruby из Creole, соответственно, вы не можете использовать лэйауты на Creole. Тем не менее, есть возможность использовать один шаблонизатор для отображения шаблона, а другой для лэйаута с помощью опции `:layout_engine`. #### CoffeeScript шаблоны
Зависимости CoffeeScript и способ запускать JavaScript
Расширения файлов .coffee
Пример coffee :index
#### Yajl шаблоны
Зависимости yajl-ruby
Расширения файлов .yajl
Пример yajl :index, :locals => { :key => 'qux' }, :callback => 'present', :variable => 'resource'
Содержимое шаблона интерпретируется как код на Ruby, а результирующая переменная json затем конвертируется с помощью `#to_json`. ```ruby json = { :foo => 'bar' } json[:baz] = key ``` Опции `:callback` и `:variable` используются для "декорирования" итогового объекта. ```ruby var resource = {"foo":"bar","baz":"qux"}; present(resource); ``` #### WLang шаблоны
Зависимости wlang
Расширения файлов .wlang
Пример wlang :index, :locals => { :key => 'value' }
Так как в WLang шаблонах невозможно вызывать методы из Ruby напрямую (за исключением `yield`), то вы почти всегда будете передавать в шаблон локальные переменные. ### Доступ к переменным в шаблонах Шаблоны интерпретируются в том же контексте, что и обработчики маршрутов. Переменные экземпляра, установленные в процессе обработки маршрутов, будут доступны напрямую в шаблонах: ```ruby get '/:id' do @foo = Foo.find(params[:id]) haml '%h1= @foo.name' end ``` Либо установите их через хеш локальных переменных: ```ruby get '/:id' do foo = Foo.find(params[:id]) haml '%h1= bar.name', :locals => { :bar => foo } end ``` Это обычный подход, когда шаблоны рендерятся как части других шаблонов. ### Шаблоны с `yield` и вложенные раскладки (layout) Раскладка (layout) обычно представляет собой шаблон, который исполняет `yield`. Такой шаблон может быть либо использован с помощью опции `:template`, как описано выше, либо он может быть дополнен блоком: ```ruby erb :post, :layout => false do erb :index end ``` Эти инструкции в основном эквивалентны `erb :index, :layout => :post`. Передача блоков интерпретирующим шаблоны методам наиболее полезна для создания вложенных раскладок: ```ruby erb :main_layout, :layout => false do erb :admin_layout do erb :user end end ``` Это же самое может быть сделано короче: ```ruby erb :admin_layout, :layout => :main_layout do erb :user end ``` В настоящее время, следующие интерпретирубщие шаблоны методы принимают блок: `erb`, `haml`, `liquid`, `slim `, `wlang`. Общий метод заполнения шаблонов `render` также принимает блок. ### Включённые шаблоны Шаблоны также могут быть определены в конце исходного файла: ```ruby require 'sinatra' get '/' do haml :index end __END__ @@ layout %html = yield @@ index %div.title Hello world. ``` Заметьте: включённые шаблоны, определенные в исходном файле, который подключил Sinatra, будут загружены автоматически. Вызовите `enable :inline_templates` напрямую, если используете включённые шаблоны в других файлах. ### Именованные шаблоны Шаблоны также могут быть определены при помощи `template` метода: ```ruby template :layout do "%html\n =yield\n" end template :index do '%div.title Hello World!' end get '/' do haml :index end ``` Если шаблон с именем "layout" существует, то он будет использоваться каждый раз при рендеринге. Вы можете отключать лэйаут в каждом конкретном случае с помощью `:layout => false` или отключить его для всего приложения: `set :haml, :layout => false`: ```ruby get '/' do haml :index, :layout => !request.xhr? end ``` ### Привязка файловых расширений Чтобы связать расширение файла с движком рендеринга, используйте `Tilt.register`. Например, если вы хотите использовать расширение `tt` для шаблонов Textile: ```ruby Tilt.register :tt, Tilt[:textile] ``` ### Добавление собственного движка рендеринга Сначала зарегистрируйте свой движок в Tilt, а затем создайте метод, отвечающий за рендеринг: ```ruby Tilt.register :myat, MyAwesomeTemplateEngine helpers do def myat(*args) render(:myat, *args) end end get '/' do myat :index end ``` Отобразит `./views/index.myat`. Чтобы узнать больше о Tilt, смотрите https://github.com/rtomayko/tilt ## Фильтры `before`-фильтры выполняются перед каждым запросом в том же контексте, что и маршруты, и могут изменять как запрос, так и ответ на него. Переменные экземпляра, установленные в фильтрах, доступны в маршрутах и шаблонах: ```ruby before do @note = 'Hi!' request.path_info = '/foo/bar/baz' end get '/foo/*' do @note #=> 'Hi!' params[:splat] #=> 'bar/baz' end ``` `after`-фильтры выполняются после каждого запроса в том же контексте и могут изменять как запрос, так и ответ на него. Переменные экземпляра, установленные в `before`-фильтрах и маршрутах, будут доступны в `after`-фильтрах: ```ruby after do puts response.status end ``` Заметьте: если вы используете метод `body`, а не просто возвращаете строку из маршрута, то тело ответа не будет доступно в `after`-фильтрах, так как оно будет сгенерировано позднее. Фильтры могут использовать шаблоны URL и будут интерпретированы, только если путь запроса совпадет с этим шаблоном: ```ruby before '/protected/*' do authenticate! end after '/create/:slug' do |slug| session[:last_slug] = slug end ``` Как и маршруты, фильтры могут использовать условия: ```ruby before :agent => /Songbird/ do # ... end after '/blog/*', :host_name => 'example.com' do # ... end ``` ## Методы-помощники Используйте метод `helpers`, чтобы определить методы-помощники, которые в дальнейшем можно будет использовать в обработчиках маршрутов и шаблонах: ```ruby helpers do def bar(name) "#{name}bar" end end get '/:name' do bar(params[:name]) end ``` Также методы-помощники могут быть заданы в отдельных модулях: ```ruby module FooUtils def foo(name) "#{name}foo" end end module BarUtils def bar(name) "#{name}bar" end end helpers FooUtils, BarUtils ``` Эффект равносилен включению модулей в класс приложения. ### Использование сессий Сессия используется, чтобы сохранять состояние между запросами. Если эта опция включена, то у вас будет один хеш сессии на одну пользовательскую сессию: ```ruby enable :sessions get '/' do "value = " << session[:value].inspect end get '/:value' do session[:value] = params[:value] end ``` Заметьте, что при использовании `enable :sessions` все данные сохраняются в куках (cookies). Это может быть не совсем то, что вы хотите (например, сохранение больших объемов данных увеличит ваш трафик). В таком случае вы можете использовать альтернативную Rack "прослойку" (middleware), реализующую механизм сессий. Для этого *не надо* вызывать `enable :sessions`, вместо этого следует подключить ее так же, как и любую другую "прослойку": ```ruby use Rack::Session::Pool, :expire_after => 2592000 get '/' do "value = " << session[:value].inspect end get '/:value' do session[:value] = params[:value] end ``` Для повышения безопасности данные сессии в куках подписываются секретным ключом. Секретный ключ генерируется Sinatra. Тем не менее, так как этот ключ будет меняться с каждым запуском приложения, вы, возможно, захотите установить ключ вручную, чтобы у всех экземпляров вашего приложения был один и тот же ключ: ```ruby set :session_secret, 'super secret' ``` Если вы хотите больше настроек для сессий, вы можете задать их, передав хеш опций в параметр `sessions`: ```ruby set :sessions, :domain => 'foo.com' ``` ### Прерывание Чтобы незамедлительно прервать обработку запроса внутри фильтра или маршрута, используйте: ```ruby halt ``` Можно также указать статус при прерывании: ```ruby halt 410 ``` Тело: ```ruby halt 'this will be the body' ``` И то, и другое: ```ruby halt 401, 'go away!' ``` Можно указать заголовки: ```ruby halt 402, {'Content-Type' => 'text/plain'}, 'revenge' ``` И, конечно, можно использовать шаблоны с `halt`: ```ruby halt erb(:error) ``` ### Передача Маршрут может передать обработку запроса следующему совпадающему маршруту, используя `pass`: ```ruby get '/guess/:who' do pass unless params[:who] == 'Frank' 'You got me!' end get '/guess/*' do 'You missed!' end ``` Блок маршрута сразу же прерывается, и контроль переходит к следующему совпадающему маршруту. Если соответствующий маршрут не найден, то ответом на запрос будет 404. ### Вызов другого маршрута Иногда `pass` не подходит, например, если вы хотите получить результат вызова другого обработчика маршрута. В таком случае просто используйте `call`: ```ruby get '/foo' do status, headers, body = call env.merge("PATH_INFO" => '/bar') [status, headers, body.map(&:upcase)] end get '/bar' do "bar" end ``` Заметьте, что в предыдущем примере можно облегчить тестирование и повысить производительность, перенеся `"bar"` в метод-помощник, используемый и в `/foo`, и в `/bar`. Если вы хотите, чтобы запрос был отправлен в тот же экземпляр приложения, а не в его копию, используйте `call!` вместо `call`. Если хотите узнать больше о `call`, смотрите спецификацию Rack. ### Задание тела, кода и заголовков ответа Хорошим тоном является установка кода состояния HTTP и тела ответа в возвращаемом значении обработчика маршрута. Тем не менее, в некоторых ситуациях вам, возможно, понадобится задать тело ответа в произвольной точке потока исполнения. Вы можете сделать это с помощью метода-помощника `body`. Если вы задействуете метод `body`, то вы можете использовать его и в дальнейшем, чтобы получить доступ к телу ответа. ```ruby get '/foo' do body "bar" end after do puts body end ``` Также можно передать блок в метод `body`, который затем будет вызван обработчиком Rack (такой подход может быть использован для реализации поточного ответа, см. "Возвращаемые значения"). Аналогично вы можете установить код ответа и его заголовки: ```ruby get '/foo' do status 418 headers \ "Allow" => "BREW, POST, GET, PROPFIND, WHEN", "Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt" body "I'm a tea pot!" end ``` Как и `body`, методы `headers` и `status`, вызванные без аргументов, возвращают свои текущие значения. ### Стриминг ответов Иногда требуется начать отправлять данные клиенту прямо в процессе генерирования частей этих данных. В особых случаях требуется постоянно отправлять данные до тех пор, пока клиент не закроет соединение. Вы можете использовать метод `stream` вместо написания собственных "оберток". ```ruby 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 ``` Что позволяет вам реализовать стриминговые API, [Server Sent Events](http://dev.w3.org/html5/eventsource/), и может служить основой для [WebSockets](http://en.wikipedia.org/wiki/WebSocket). Также такой подход можно использовать для увеличения производительности в случае, когда какая-то часть контента зависит от медленного ресурса. Заметьте, что возможности стриминга, особенно количество одновременно обслуживаемых запросов, очень сильно зависят от используемого веб-сервера. Некоторые серверы, например, WEBRick, могут и вовсе не поддерживать стриминг. Если сервер не поддерживает стриминг, то все данные будут отправлены за один раз сразу после того, как блок, переданный в `stream`, завершится. Стриминг вообще не работает при использовании Shotgun. Если метод используется с параметром `keep_open`, то он не будет вызывать `close` у объекта потока, что позволит вам закрыть его позже в любом другом месте. Это работает только с событийными серверами, например, с Thin и Rainbows. Другие же серверы все равно будут закрывать поток: ```ruby # long polling set :server, :thin connections = [] get '/subscribe' do # регистрация клиента stream(:keep_open) { |out| connections << out } # удаление "мертвых клиентов" connections.reject!(&:closed?) # допуск "subscribed" end post '/message' do connections.each do |out| # уведомить клиента о новом сообщении out << params[:message] << "\n" # указать клиенту на необходимость снова соединиться out.close end # допуск "message received" end ``` ### Логирование В области видимости запроса метод `logger` предоставляет доступ к экземпляру `Logger`: ```ruby get '/' do logger.info "loading data" # ... end ``` Этот логер автоматически учитывает ваши настройки логирования в Rack. Если логирование выключено, то этот метод вернет пустой (dummy) объект, поэтому вы можете смело использовать его в маршрутах и фильтрах. Заметьте, что логирование включено по умолчанию только для `Sinatra::Application`, а если ваше приложение — подкласс `Sinatra::Base`, то вы, наверное, захотите включить его вручную: ```ruby class MyApp < Sinatra::Base configure :production, :development do enable :logging end end ``` Чтобы избежать использования любой логирующей "прослойки", задайте опции `logging` значение `nil`. Тем не менее, не забывайте, что в такой ситуации `logger` вернет `nil`. Чаще всего так делают, когда задают свой собственный логер. Sinatra будет использовать то, что находится в `env['rack.logger']`. ### Mime-типы Когда вы используете `send_file` или статические файлы, у вас могут быть mime-типы, которые Sinatra не понимает по умолчанию. Используйте `mime_type` для их регистрации по расширению файла: ```ruby configure do mime_type :foo, 'text/foo' end ``` Вы также можете использовать это в `content_type` методе-помощнике: ```ruby get '/' do content_type :foo "foo foo foo" end ``` ### Генерирование URL Чтобы сформировать URL, вам следует использовать метод `url`, например, в Haml: ```ruby %a{:href => url('/foo')} foo ``` Этот метод учитывает обратные прокси и маршрутизаторы Rack, если они присутствуют. Наряду с `url` вы можете использовать `to` (смотрите пример ниже). ### Перенаправление (редирект) Вы можете перенаправить браузер пользователя с помощью метода `redirect`: ```ruby get '/foo' do redirect to('/bar') end ``` Любые дополнительные параметры используются по аналогии с аргументами метода `halt`: ```ruby redirect to('/bar'), 303 redirect 'http://google.com', 'wrong place, buddy' ``` Вы также можете перенаправить пользователя обратно, на страницу, с которой он пришел, с помощью `redirect back`: ```ruby get '/foo' do "do something" end get '/bar' do do_something redirect back end ``` Чтобы передать какие-либо параметры вместе с перенаправлением, либо добавьте их в строку запроса: ```ruby redirect to('/bar?sum=42') ``` либо используйте сессию: ```ruby enable :sessions get '/foo' do session[:secret] = 'foo' redirect to('/bar') end get '/bar' do session[:secret] end ``` ### Управление кэшированием Установка корректных заголовков — основа правильного HTTP кэширования. Вы можете легко выставить заголовок Cache-Control таким образом: ```ruby get '/' do cache_control :public "cache it!" end ``` Совет: задавайте кэширование в `before`-фильтре: ```ruby before do cache_control :public, :must_revalidate, :max_age => 60 end ``` Если вы используете метод `expires` для задания соответствующего заголовка, то `Cache-Control` будет выставлен автоматически: ```ruby before do expires 500, :public, :must_revalidate end ``` Чтобы как следует использовать кэширование, вам следует подумать об использовании `etag` или `last_modified`. Рекомендуется использовать эти методы-помощники *до* выполнения ресурсоемких вычислений, так как они немедленно отправят ответ клиенту, если текущая версия уже есть в их кэше: ```ruby get '/article/:id' do @article = Article.find params[:id] last_modified @article.updated_at etag @article.sha1 erb :article end ``` Также вы можете использовать [weak ETag](http://en.wikipedia.org/wiki/HTTP_ETag#Strong_and_weak_validation): ```ruby etag @article.sha1, :weak ``` Эти методы-помощники не станут ничего кэшировать для вас, но они дадут необходимую информацию для вашего кэша. Если вы ищете легкое решение для кэширования, попробуйте [rack-cache](https://github.com/rtomayko/rack-cache): ```ruby require 'rack/cache' require 'sinatra' use Rack::Cache get '/' do cache_control :public, :max_age => 36000 sleep 5 "hello" end ``` Используйте опцию `:static_cache_control` (см. ниже), чтобы добавить заголовок `Cache-Control` к статическим файлам. В соответствии с RFC 2616 ваше приложение должно вести себя по-разному, когда заголовки If-Match или If-None-Match имеют значение `*`, в зависимости от того, существует или нет запрашиваемый ресурс. Sinatra предполагает, что ресурсы, к которым обращаются с помощью безопасных (GET) и идемпотентных (PUT) методов, уже существуют, а остальные ресурсы (к которым обращаются, например, с помощью POST) считает новыми. Вы можете изменить данное поведение с помощью опции `:new_resource`: ```ruby get '/create' do etag '', :new_resource => true Article.create erb :new_article end ``` Если вы хотите использовать weak ETag, задайте опцию `:kind`: ```ruby etag '', :new_resource => true, :kind => :weak ``` ### Отправка файлов Для отправки файлов пользователю вы можете использовать метод `send_file`: ```ruby get '/' do send_file 'foo.png' end ``` Этот метод имеет несколько опций: ```ruby send_file 'foo.png', :type => :jpg ``` Возможные опции:
filename
имя файла, по умолчанию: реальное имя файла.
last_modified
значение для заголовка Last-Modified, по умолчанию: mtime (время изменения) файла.
type
тип файла, по умолчанию: определяется по расширению файла.
disposition
используется для заголовка Content-Disposition, возможные значения: nil (по умолчанию), :attachment и :inline.
length
значения для заголовка Content-Length, по умолчанию: размер файла.
status
Код ответа. Полезно, когда отдается статический файл в качестве страницы с сообщением об ошибке.
Этот метод будет использовать возможности Rack сервера для отправки файлов, если они доступны, в противном случае будет напрямую отдавать файл из Ruby процесса. Метод `send_file` также обеспечивает автоматическую обработку частичных (range) запросов с помощью Sinatra. ### Доступ к объекту запроса Объект входящего запроса доступен на уровне обработки запроса (в фильтрах, маршрутах, обработчиках ошибок) с помощью `request` метода: ```ruby # приложение запущено на 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.scheme # "http" request.script_name # "/example" request.path_info # "/foo" request.port # 80 request.request_method # "GET" request.query_string # "" request.content_length # длина тела запроса request.media_type # медиатип тела запроса request.host # "example.com" request.get? # true (есть аналоги для других методов HTTP) request.form_data? # false request["some_param"] # значение параметра some_param. Шорткат для хеша params request.referrer # источник запроса клиента либо '/' request.user_agent # user agent (используется для :agent условия) request.cookies # хеш, содержащий cookies браузера request.xhr? # является ли запрос ajax запросом? request.url # "http://example.com/example/foo" request.path # "/example/foo" request.ip # IP-адрес клиента request.secure? # false (true, если запрос сделан через SSL) request.forwarded? # true (если сервер работает за обратным прокси) request.env # "сырой" env хеш, полученный Rack end ``` Некоторые опции, такие как `script_name` или `path_info`, доступны для изменения: ```ruby before { request.path_info = "/" } get "/" do "all requests end up here" end ``` `request.body` является IO или StringIO объектом: ```ruby post "/api" do request.body.rewind # в случае, если кто-то уже прочитал тело запроса data = JSON.parse request.body.read "Hello #{data['name']}!" end ``` ### Вложения Вы можете использовать метод `attachment`, чтобы сказать браузеру, что ответ сервера должен быть сохранен на диск, а не отображен: ```ruby get '/' do attachment "store it!" end ``` Вы также можете указать имя файла: ```ruby get '/' do attachment "info.txt" "store it!" end ``` ### Работа со временем и датами Sinatra предлагает метод-помощник `time_for`, который из заданного значения создает объект Time. Он также может конвертировать `DateTime`, `Date` и подобные классы: ```ruby get '/' do pass if Time.now > time_for('Dec 23, 2012') "still time" end ``` Этот метод используется внутри Sinatra методами `expires`, `last_modified` и им подобными. Поэтому вы легко можете расширить функционал этих методов, переопределив `time_for` в своем приложении: ```ruby helpers do def time_for(value) case value when :yesterday then Time.now - 24*60*60 when :tomorrow then Time.now + 24*60*60 else super end end end get '/' do last_modified :yesterday expires :tomorrow "hello" end ``` ### Поиск шаблонов Для поиска шаблонов и их последующего рендеринга используется метод `find_template`: ```ruby find_template settings.views, 'foo', Tilt[:haml] do |file| puts "could be #{file}" end ``` Это не слишком полезный пример. Зато полезен тот факт, что вы можете переопределить этот метод, чтобы использовать свой собственный механизм поиска. Например, если вы хотите, чтобы можно было использовать несколько директорий с шаблонами: ```ruby set :views, ['views', 'templates'] helpers do def find_template(views, name, engine, &block) Array(views).each { |v| super(v, name, engine, &block) } end end ``` Другой пример, в котором используются разные директории для движков рендеринга: ```ruby set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views' helpers do def find_template(views, name, engine, &block) _, folder = views.detect { |k,v| engine == Tilt[k] } folder ||= views[:default] super(folder, name, engine, &block) end end ``` Вы можете легко вынести этот код в расширение и поделиться им с остальными! Заметьте, что `find_template` не проверяет, существует ли файл на самом деле, а вызывает заданный блок для всех возможных путей. Дело тут не в производительности, дело в том, что `render` вызовет `break`, как только файл не будет найден. Содержимое и местонахождение шаблонов будет закэшировано, если приложение запущено не в режиме разработки (`set :environment, :development`). Вы должны помнить об этих нюансах, если пишите по-настоящему "сумасшедший" метод. ## Конфигурация Этот блок исполняется один раз при старте в любом окружении, режиме (environment): ```ruby configure do # задание одной опции set :option, 'value' # устанавливаем несколько опций set :a => 1, :b => 2 # то же самое, что и `set :option, true` enable :option # то же самое, что и `set :option, false` disable :option # у вас могут быть "динамические" опции с блоками set(:css_dir) { File.join(views, 'css') } end ``` Будет запущено, когда окружение (RACK_ENV переменная) `:production`: ```ruby configure :production do ... end ``` Будет запущено, когда окружение `:production` или `:test`: ```ruby configure :production, :test do ... end ``` Вы можете получить доступ к этим опциям с помощью `settings`: ```ruby configure do set :foo, 'bar' end get '/' do settings.foo? # => true settings.foo # => 'bar' ... end ``` ### Настройка защиты от атак Sinatra использует [Rack::Protection](https://github.com/rkh/rack-protection#readme) для защиты приложения от простых атак. Вы можете легко выключить эту защиту (что сделает ваше приложение чрезвычайно уязвимым): ```ruby disable :protection ``` Чтобы пропустить какой-либо уровень защиты, передайте хеш опций в параметр `protection`: ```ruby set :protection, :except => :path_traversal ``` Вы также можете отключить сразу несколько уровней защиты: ```ruby set :protection, :except => [:path_traversal, :session_hijacking] ``` ### Доступные настройки
absolute_redirects
если отключено, то Sinatra будет позволять использование относительных перенаправлений, но при этом перестанет соответствовать RFC 2616 (HTTP 1.1), который разрешает только абсолютные перенаправления.
Включайте эту опцию, если ваше приложение работает за обратным прокси, который настроен не совсем корректно. Обратите внимание, метод url все равно будет генерировать абсолютные URL, если вы не передадите false вторым аргументом.
Отключено по умолчанию.
add_charsets
mime-типы, к которым метод content_type будет автоматически добавлять информацию о кодировке. Вам следует добавлять значения к этой опции вместо ее переопределения: settings.add_charsets << "application/foobar"
app_file
путь к главному файлу приложения, используется для нахождения корневой директории проекта, директорий с шаблонами и статическими файлами, вложенных шаблонов.
bind
используемый IP-адрес (по умолчанию: 0.0.0.0). Используется только встроенным сервером.
default_encoding
кодировка, если неизвестна (по умолчанию: "utf-8").
dump_errors
отображать ошибки в логе.
environment
текущее окружение, по умолчанию, значение ENV['RACK_ENV'] или "development", если ENV['RACK_ENV'] недоступна.
logging
использовать логер.
lock
создает блокировку для каждого запроса, которая гарантирует обработку только одного запроса в текущий момент времени в Ruby процессе.
Включайте, если ваше приложение не потоко-безопасно (thread-safe). Отключено по умолчанию.
method_override
использовать "магический" параметр _method, для поддержки PUT/DELETE форм в браузерах, которые не поддерживают эти методы.
port
порт, на котором будет работать сервер. Используется только встроенным сервером.
prefixed_redirects
добавлять или нет параметр request.script_name к редиректам, если не задан абсолютный путь. Таким образом, redirect '/foo' будет вести себя как redirect to('/foo'). Отключено по умолчанию.
protection
включена или нет защита от атак. Смотрите секцию выше.
public_dir
Алиас для public_folder.
public_folder
путь к директории, откуда будут раздаваться статические файлы. Используется, только если включена раздача статических файлов (см. опцию static ниже).
reload_templates
перезагружать или нет шаблоны на каждый запрос. Включено в режиме разработки.
root
путь к корневой директории проекта.
raise_errors
выбрасывать исключения (будет останавливать приложение). По умолчанию включено только в окружении test.
run
если включено, Sinatra будет самостоятельно запускать веб-сервер. Не включайте, если используете rackup или аналогичные средства.
running
работает ли сейчас встроенный сервер? Не меняйте эту опцию!
server
сервер или список серверов, которые следует использовать в качестве встроенного сервера. По умолчанию: ['thin', 'mongrel', 'webrick'], порядок задает приоритет.
sessions
включить сессии на основе кук (cookie) на базе Rack::Session::Cookie. Смотрите секцию "Использование сессий" выше.
show_exceptions
показывать исключения/стек вызовов (stack trace) в браузере. По умолчанию включено только в окружении development.
Может быть установлено в :after_handler для запуска специфичной для приложения обработки ошибок, перед показом трассировки стека в браузере.
static
должна ли Sinatra осуществлять раздачу статических файлов.
Отключите, когда используете какой-либо веб-сервер для этой цели.
Отключение значительно улучшит производительность приложения.
По умолчанию включено в классических и отключено в модульных приложениях.
static_cache_control
когда Sinatra отдает статические файлы, используйте эту опцию, чтобы добавить им заголовок Cache-Control. Для этого используется метод-помощник cache_control. По умолчанию отключено.
Используйте массив, когда надо задать несколько значений: set :static_cache_control, [:public, :max_age => 300]
threaded
если включено, то Thin будет использовать EventMachine.defer для обработки запросов.
views
путь к директории с шаблонами.
## Режим, окружение Есть 3 предопределенных режима, окружения: `"development"`, `"production"` и `"test"`. Режим может быть задан через переменную окружения `RACK_ENV`. Значение по умолчанию — `"development"`. В этом режиме работы все шаблоны перезагружаются между запросами. А также задаются специальные обработчики `not_found` и `error`, чтобы вы могли увидеть стек вызовов. В окружениях `"production"` и `"test"` шаблоны по умолчанию кэшируются. Для запуска приложения в определенном окружении используйте ключ `-e` ``` ruby my_app.rb -e [ENVIRONMENT] ``` Вы можете использовать предопределенные методы `development?`, `test?` и +production?, чтобы определить текущее окружение. ## Обработка ошибок Обработчики ошибок исполняются в том же контексте, что и маршруты, и `before`-фильтры, а это означает, что всякие прелести вроде `haml`, `erb`, `halt` и т.д. доступны и им. ### Not Found Когда выброшено исключение `Sinatra::NotFound`, или кодом ответа является 404, то будет вызван `not_found` обработчик: ```ruby not_found do 'This is nowhere to be found.' end ``` ### Ошибки Обработчик ошибок `error` будет вызван, когда исключение выброшено из блока маршрута, либо из фильтра. Объект-исключение доступен как переменная `sinatra.error` в Rack: ```ruby error do 'Sorry there was a nasty error - ' + env['sinatra.error'].name end ``` Конкретные ошибки: ```ruby error MyCustomError do 'So what happened was...' + env['sinatra.error'].message end ``` Тогда, если это произошло: ```ruby get '/' do raise MyCustomError, 'something bad' end ``` То вы получите: ``` So what happened was... something bad ``` Также вы можете установить обработчик ошибок для кода состояния HTTP: ```ruby error 403 do 'Access forbidden' end get '/secret' do 403 end ``` Либо набора кодов: ```ruby error 400..510 do 'Boom' end ``` Sinatra устанавливает специальные `not_found` и `error` обработчики, когда приложение запущено в режиме разработки (окружение `:development`). ## Rack "прослойки" Sinatra использует [Rack](http://rack.rubyforge.org/), минимальный стандартный интерфейс для веб-фреймворков на Ruby. Одной из самых интересных для разработчиков возможностей Rack является поддержка "прослоек" ("middleware") — компонентов, находящихся "между" сервером и вашим приложением, которые отслеживают и/или манипулируют HTTP запросами/ответами для предоставления различной функциональности. В Sinatra очень просто использовать такие "прослойки" с помощью метода `use`: ```ruby require 'sinatra' require 'my_custom_middleware' use Rack::Lint use MyCustomMiddleware get '/hello' do 'Hello World' end ``` Семантика `use` идентична той, что определена для [Rack::Builder](http://rack.rubyforge.org/doc/classes/Rack/Builder.html) DSL (чаще всего используется в rackup файлах). Например, метод `use` принимает как множественные переменные, так и блоки: ```ruby use Rack::Auth::Basic do |username, password| username == 'admin' && password == 'secret' end ``` Rack распространяется с различными стандартными "прослойками" для логирования, отладки, маршрутизации URL, аутентификации, обработки сессий. Sinatra использует многие из этих компонентов автоматически, основываясь на конфигурации, чтобы вам не приходилось подключать (`use`) их вручную. Вы можете найти полезные прослойки в [rack](https://github.com/rack/rack/tree/master/lib/rack), [rack-contrib](https://github.com/rack/rack-contrib#readme), [CodeRack](http://coderack.org/) или в [Rack wiki](https://github.com/rack/rack/wiki/List-of-Middleware). ## Тестирование Тесты для Sinatra приложений могут быть написаны с помощью библиотек, фреймворков, поддерживающих тестирование Rack. [Rack::Test](http://rdoc.info/github/brynary/rack-test/master/frames) рекомендован: ```ruby require 'my_sinatra_app' require 'test/unit' require 'rack/test' class MyAppTest < Test::Unit::TestCase include Rack::Test::Methods def app Sinatra::Application end def test_my_default get '/' assert_equal 'Hello World!', last_response.body end def test_with_params get '/meet', :name => 'Frank' assert_equal 'Hello Frank!', last_response.body end def test_with_rack_env get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' assert_equal "You're using Songbird!", last_response.body end end ``` ## Sinatra::Base — "прослойки", библиотеки и модульные приложения Описание своего приложения самым простейшим способом (с помощью DSL верхнего уровня, классический стиль) отлично работает для крохотных приложений. В таких случаях используется конфигурация, рассчитанная на микро-приложения (единственный файл приложения, `./public` и `./views` директории, логирование, страница информации об исключении и т.д.). Тем не менее, такой метод имеет множество недостатков при создании компонентов, таких как Rack middleware ("прослоек"), Rails metal, простых библиотек с серверными компонентами, расширений Sinatra. И тут на помощь приходит `Sinatra::Base`: ```ruby require 'sinatra/base' class MyApp < Sinatra::Base set :sessions, true set :foo, 'bar' get '/' do 'Hello world!' end end ``` Методы, доступные `Sinatra::Base` подклассам идентичны тем, что доступны приложениям в DSL верхнего уровня. Большинство таких приложений могут быть конвертированы в `Sinatra::Base` компоненты с помощью двух модификаций: * Вы должны подключать `sinatra/base` вместо `sinatra`, иначе все методы, предоставляемые Sinatra, будут импортированы в глобальное пространство имен. * Поместите все маршруты, обработчики ошибок, фильтры и опции в подкласс `Sinatra::Base`. `Sinatra::Base` — это чистый лист. Большинство опций, включая встроенный сервер, по умолчанию отключены. Смотрите [Опции и конфигурация](http://www.sinatrarb.com/configuration.html) для детальной информации об опциях и их поведении. ### Модульные приложения против классических Вопреки всеобщему убеждению, в классическом стиле (самом простом) нет ничего плохого. Если этот стиль подходит вашему приложению, вы не обязаны переписывать его в модульное приложение. Основным недостатком классического стиля является тот факт, что у вас может быть только одно приложение Sinatra на один процесс Ruby. Если вы планируете использовать больше, переключайтесь на модульный стиль. Вы можете смело смешивать модульный и классический стили. Переходя с одного стиля на другой, примите во внимание следующие изменения в настройках: Опция Классический Модульный app_file файл с приложением файл с подклассом Sinatra::Base run $0 == app_file false logging true false method_override true false inline_templates true false static true false ### Запуск модульных приложений Есть два общепринятых способа запускать модульные приложения: запуск напрямую с помощью `run!`: ```ruby # my_app.rb require 'sinatra/base' class MyApp < Sinatra::Base # ... здесь код приложения ... # запускаем сервер, если исполняется текущий файл run! if app_file == $0 end ``` Затем: ``` ruby my_app.rb ``` Или с помощью конфигурационного файла `config.ru`, который позволяет использовать любой Rack-совместимый сервер приложений. ```ruby # config.ru require './my_app' run MyApp ``` Запускаем: ``` rackup -p 4567 ``` ### Запуск классических приложений с config.ru Файл приложения: ```ruby # app.rb require 'sinatra' get '/' do 'Hello world!' end ``` И соответствующий `config.ru`: ```ruby require './app' run Sinatra::Application ``` ### Когда использовать config.ru? Вот несколько причин, по которым вы, возможно, захотите использовать `config.ru`: * вы хотите разворачивать свое приложение на различных Rack-совместимых серверах (Passenger, Unicorn, Heroku, ...); * вы хотите использовать более одного подкласса `Sinatra::Base`; * вы хотите использовать Sinatra только в качестве "прослойки" Rack. **Совсем необязательно переходить на использование `config.ru` лишь потому, что вы стали использовать модульный стиль приложения. И необязательно использовать модульный стиль, чтобы запускать приложение с помощью `config.ru`.** ### Использование Sinatra в качестве "прослойки" Не только сама Sinatra может использовать "прослойки" Rack, но и любое Sinatra приложение само может быть добавлено к любому Rack endpoint в качестве "прослойки". Этим endpoint (конечной точкой) может быть другое Sinatra приложение, или приложение, основанное на Rack (Rails/Ramaze/Camping/...): ```ruby require 'sinatra/base' class LoginScreen < Sinatra::Base enable :sessions get('/login') { haml :login } post('/login') do if params[:name] == 'admin' && params[:password] == 'admin' session['user_name'] = params[:name] else redirect '/login' end end end class MyApp < Sinatra::Base # "прослойка" будет запущена перед фильтрами use LoginScreen before do unless session['user_name'] halt "Access denied, please login." end end get('/') { "Hello #{session['user_name']}." } end ``` ### Создание приложений "на лету" Иногда требуется создавать Sinatra приложения "на лету" (например, из другого приложения). Это возможно с помощью `Sinatra.new`: ```ruby require 'sinatra/base' my_app = Sinatra.new { get('/') { "hi" } } my_app.run! ``` Этот метод может принимать аргументом приложение, от которого следует наследоваться: ```ruby # config.ru require 'sinatra/base' controller = Sinatra.new do enable :logging helpers MyHelpers end map('/a') do run Sinatra.new(controller) { get('/') { 'a' } } end map('/b') do run Sinatra.new(controller) { get('/') { 'b' } } end ``` Это особенно полезно для тестирования расширений Sinatra и при использовании Sinatra внутри вашей библиотеки. Благодаря этому, использовать Sinatra как "прослойку" очень просто: ```ruby require 'sinatra/base' use Sinatra do get('/') { ... } end run RailsProject::Application ``` ## Области видимости и привязка Текущая область видимости определяет методы и переменные, доступные в данный момент. ### Область видимости приложения / класса Любое Sinatra приложение соответствует подклассу `Sinatra::Base`. Если вы используете DSL верхнего уровня (`require 'sinatra'`), то этим классом будет `Sinatra::Application`, иначе это будет подкласс, который вы создали вручную. На уровне класса вам будут доступны такие методы, как `get` или `before`, но вы не сможете получить доступ к объектам `request` или `session`, так как существует только один класс приложения для всех запросов. Опции, созданные с помощью `set`, являются методами уровня класса: ```ruby class MyApp < Sinatra::Base # Я в области видимости приложения! set :foo, 42 foo # => 42 get '/foo' do # Я больше не в области видимости приложения! end end ``` У вас будет область видимости приложения внутри: * тела вашего класса приложения; * методов, определенных расширениями; * блока, переданного в `helpers`; * блоков, использованных как значения для `set`; * блока, переданного в `Sinatra.new`. Вы можете получить доступ к объекту области видимости (классу приложения) следующими способами: * через объект, переданный блокам конфигурации (`configure { |c| ... }`); * `settings` внутри области видимости запроса. ### Область видимости запроса/экземпляра Для каждого входящего запроса будет создан новый экземпляр вашего приложения, и все блоки обработчика будут запущены в этом контексте. В этой области видимости вам доступны `request` и `session` объекты, вызовы методов рендеринга, такие как `erb` или `haml`. Вы можете получить доступ к области видимости приложения из контекста запроса, используя метод-помощник `settings`: ```ruby class MyApp < Sinatra::Base # Я в области видимости приложения! get '/define_route/:name' do # Область видимости запроса '/define_route/:name' @value = 42 settings.get("/#{params[:name]}") do # Область видимости запроса "/#{params[:name]}" @value # => nil (другой запрос) end "Route defined!" end end ``` У вас будет область видимости запроса в: * get/head/post/put/delete/options блоках; * before/after фильтрах; * методах-помощниках; * шаблонах/отображениях. ### Область видимости делегирования Область видимости делегирования просто перенаправляет методы в область видимости класса. Однако, она не полностью ведет себя как область видимости класса, так как у вас нет привязки к классу. Только методы, явно помеченные для делегирования, будут доступны, а переменных/состояний области видимости класса не будет (иначе говоря, у вас будет другой `self` объект). Вы можете непосредственно добавить методы делегирования, используя `Sinatra::Delegator.delegate :method_name`. У вас будет контекст делегирования внутри: * привязки верхнего уровня, если вы сделали `require 'sinatra'`; * объекта, расширенного с помощью `Sinatra::Delegator`. Посмотрите сами в код: вот [примесь Sinatra::Delegator](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/base.rb#L1609-1633) [расширяет главный объект](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/main.rb#L28-30). ## Командная строка Sinatra приложения могут быть запущены напрямую: ``` ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER] ``` Опции включают: ``` -h # раздел помощи -p # указание порта (по умолчанию 4567) -o # указание хоста (по умолчанию 0.0.0.0) -e # указание окружения, режима (по умолчанию development) -s # указание rack сервера/обработчика (по умолчанию thin) -x # включить мьютекс-блокировку (по умолчанию выключена) ``` ## Системные требования Следующие версии Ruby официально поддерживаются:
Ruby 1.8.7
1.8.7 полностью поддерживается, тем не менее, если вас ничто не держит на этой версии, рекомендуем обновиться до 1.9.2 или перейти на JRuby или Rubinius. Поддержка 1.8.7 не будет прекращена до выхода Sinatra 2.0 и Ruby 2.0, разве что в случае релиза 1.8.8 (что маловероятно). Но даже тогда, возможно, поддержка не будет прекращена. Ruby 1.8.6 больше не поддерживается. Если вы хотите использовать 1.8.6, откатитесь до Sinatra 1.2, которая будет получать все исправления ошибок до тех пор, пока не будет выпущена Sinatra 1.4.0.
Ruby 1.9.2
1.9.2 полностью поддерживается и рекомендована к использованию. Не используйте 1.9.2p0, известно, что эта версия очень нестабильна при использовании Sinatra. Эта версия будет поддерживаться по крайней мере до выхода Ruby 1.9.4/2.0, а поддержка последней версии 1.9 будет осуществляться до тех пор, пока она поддерживается командой разработчиков Ruby.
Ruby 1.9.3
1.9.3 полностью поддерживается. Заметьте, что переход на 1.9.3 с ранних версий сделает недействительными все сессии.
Rubinius
Rubinius официально поддерживается (Rubinius >= 1.2.4), всё, включая все языки шаблонов, работает. Предстоящий релиз 2.0 также поддерживается.
JRuby
JRuby официально поддерживается (JRuby >= 1.6.5). Нет никаких проблем с использованием альтернативных шаблонов. Тем не менее, если вы выбираете JRuby, то, пожалуйста, посмотрите на JRuby Rack-серверы, так как Thin не поддерживается полностью на JRuby. Поддержка расширений на C в JRuby все еще экспериментальная, что на данный момент затрагивает только RDiscount, Redcarpet и RedCloth.
Мы также следим за предстоящими к выходу версиями Ruby. Следующие реализации Ruby не поддерживаются официально, но известно, что на них запускается Sinatra: * старые версии JRuby и Rubinius; * Ruby Enterprise Edition; * MacRuby, Maglev, IronRuby; * Ruby 1.9.0 и 1.9.1 (настоятельно не рекомендуются к использованию). То, что версия официально не поддерживается, означает, что, если что-то не работает на этой версии, а на поддерживаемой работает — это не наша проблема, а их. Мы также запускаем наши CI-тесты на версии Ruby, находящейся в разработке (предстоящей 2.0.0), и на 1.9.4, но мы не можем ничего гарантировать, так как они находятся в разработке. Предполагается, что 1.9.4p0 и 2.0.0p0 будут поддерживаться. Sinatra должна работать на любой операционной системе, в которой есть одна из указанных выше версий Ruby. Пока невозможно запустить Sinatra на Cardinal, SmallRuby, BlueRuby и на любой версии Ruby до 1.8.7. ## На острие Если вы хотите использовать самый последний код Sinatra, не бойтесь запускать свое приложение вместе с кодом из master ветки Sinatra, она весьма стабильна. Мы также время от времени выпускаем предварительные версии, так что вы можете делать так: ``` gem install sinatra --pre ``` Чтобы воспользоваться некоторыми самыми последними возможностями. ### С помощью Bundler Если вы хотите запускать свое приложение с последней версией Sinatra, то рекомендуем использовать [Bundler](http://gembundler.com/). Сначала установите Bundler, если у вас его еще нет: ``` gem install bundler ``` Затем создайте файл `Gemfile` в директории вашего проекта: ```ruby source :rubygems gem 'sinatra', :git => "git://github.com/sinatra/sinatra.git" # другие зависимости gem 'haml' # например, если используете haml gem 'activerecord', '~> 3.0' # может быть, вам нужен и ActiveRecord 3.x ``` Обратите внимание, вам нужно будет указывать все зависимости вашего приложения в этом файле. Однако, непосредственные зависимости Sinatra (Rack и Tilt) Bundler автоматически скачает и добавит. Теперь вы можете запускать свое приложение так: ``` bundle exec ruby myapp.rb ``` ### Вручную Создайте локальный клон репозитория и запускайте свое приложение с `sinatra/lib` директорией в `$LOAD_PATH`: ``` cd myapp git clone git://github.com/sinatra/sinatra.git ruby -Isinatra/lib myapp.rb ``` Чтобы обновить исходники Sinatra: ``` cd myapp/sinatra git pull ``` ### Установка глобально Вы можете самостоятельно собрать gem: ``` git clone git://github.com/sinatra/sinatra.git cd sinatra rake sinatra.gemspec rake install ``` Если вы устанавливаете пакеты (gem) от пользователя root, то вашим последним шагом должна быть команда ``` sudo rake install ``` ## Версии Sinatra использует [Semantic Versioning](http://semver.org/), SemVer и SemVerTag. ## Дальнейшее чтение * [Веб-сайт проекта](http://www.sinatrarb.com/) — Дополнительная документация, новости и ссылки на другие ресурсы. * [Участие в проекте](http://www.sinatrarb.com/contributing) — Обнаружили баг? Нужна помощь? Написали патч? * [Слежение за проблемами/ошибками](http://github.com/sinatra/sinatra/issues) * [Twitter](http://twitter.com/sinatra) * [Группы рассылки](http://groups.google.com/group/sinatrarb/topics) * [#sinatra](irc://chat.freenode.net/#sinatra) на http://freenode.net * [Sinatra Book](http://sinatra-book.gittr.com) учебник и сборник рецептов * [Sinatra Recipes](http://recipes.sinatrarb.com/) сборник рецептов * API документация к [последнему релизу](http://rubydoc.info/gems/sinatra) или [текущему HEAD](http://rubydoc.info/github/sinatra/sinatra) на http://rubydoc.info * [Сервер непрерывной интеграции](http://travis-ci.org/sinatra/sinatra) sinatra-1.4.3/README.fr.md0000644000004100000410000021122212161612727015102 0ustar www-datawww-data# Sinatra *Attention : Ce document correspond à la traduction de la version anglaise et il n'est peut être plus à jour.* Sinatra est un [DSL](http://fr.wikipedia.org/wiki/Langage_dédié) pour créer rapidement et facilement des applications web en Ruby : ```ruby # mon_application.rb require 'sinatra' get '/' do 'Bonjour le monde !' end ``` Installez la gem et lancez avec : ```bash $ gem install sinatra $ ruby mon_application.rb ``` Le résultat est visible sur : http://localhost:4567 Il est recommandé d'exécuter également `gem install thin`, pour que Sinatra utilise le server Thin quand il est disponible. ## Routes Dans Sinatra, une route est une méthode HTTP couplée à un masque (pattern) URL. Chaque route est associée à un bloc : ```ruby get '/' do .. montrer quelque chose .. end post '/' do .. créer quelque chose .. end put '/' do .. remplacer quelque chose .. end patch '/' do .. changer quelque chose .. end delete '/' do .. effacer quelque chose .. end options '/' do .. apaiser quelquechose .. end ``` Les routes sont évaluées dans l'ordre où elles ont été définies. La première route qui correspond à la requête est appelée. Les masques peuvent inclure des paramètres nommés, accessibles par l'intermédiaire du hash `params` : ```ruby get '/bonjour/:nom' do # répond aux requêtes "GET /bonjour/foo" et "GET /bonjour/bar" # params[:nom] est 'foo' ou 'bar' "Bonjour #{params[:nom]} !" end ``` Vous pouvez aussi accéder aux paramètres nommés directement grâce aux paramètres du bloc comme ceci : ```ruby get '/bonjour/:nom' do |n| "Bonjour #{n} !" end ``` Une route peut contenir un splat (caractère joker), accessible par l'intermédiaire du tableau `params[:splat]` : ```ruby get '/dire/*/a/*' do # répond à /dire/bonjour/a/monde params[:splat] # => ["bonjour", "monde"] end get '/telecharger/*.*' do # répond à /telecharger/chemin/vers/fichier.xml params[:splat] # => ["chemin/vers/fichier", "xml"] end ``` Ou par l'intermédiaire des paramètres du bloc : ```ruby get '/telecharger/*.*' do |chemin, ext| [chemin, ext] # => ["path/to/file", "xml"] end ``` Une route peut aussi être définie par une expression régulière : ```ruby get %r{/bonjour/([\w]+)} do "Bonjour, #{params[:captures].first} !" end ``` Là encore on peut utiliser les paramètres de bloc : ```ruby get %r{/bonjour/([\w]+)} do |c| "Bonjour, #{c} !" end ``` Les routes peuvent aussi comporter des paramètres optionnels : ```ruby get '/posts.?:format?' do # répond à "GET /posts" et aussi à "GET /posts.json", "GET /posts.xml" etc... end ``` A ce propos, à moins d'avoir désactivé la protection contre les attaques par "path transversal" (voir plus loin), l'URL demandée peut avoir été modifiée avant d'être comparée à vos routes. ### Conditions Les routes peuvent définir toutes sortes de conditions, comme par exemple le "user agent" : ```ruby get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do "Vous utilisez Songbird version #{params[:agent][0]}" end get '/foo' do # Correspond à tous les autres navigateurs end ``` Les autres conditions disponibles sont `host_name` et `provides` : ```ruby get '/', :host_name => /^admin\./ do "Zone Administrateur, Accès refusé !" end get '/', :provides => 'html' do haml :index end get '/', :provides => ['rss', 'atom', 'xml'] do builder :feed end ``` Vous pouvez facilement définir vos propres conditions : ```ruby set(:probability) { |value| condition { rand <= value } } get '/gagner_une_voiture', :probability => 0.1 do "Vous avez gagné !" end get '/gagner_une_voiture' do "Désolé, vous avez perdu." end ``` Utilisez un splat (caractère joker) dans le cas d'une condition qui prend plusieurs valeurs : ```ruby 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 la réponse qui sera transmise au client HTTP ou du moins au prochain middleware dans la pile Rack. Le plus souvent, il s'agit d'une chaîne de caractères, comme dans les exemples précédents. Cependant, d'autres valeurs sont acceptées. Vous pouvez renvoyer n'importe quel objet qu'il s'agisse d'une réponse Rack valide, d'un corps de réponse Rack ou d'un code statut HTTP : * Un tableau de 3 éléments : `[code statut (Fixnum), entêtes (Hash), corps de la réponse (répondant à #each)]` * Un tableau de 2 élements : `[code statut (Fixnum), corps de la réponse (répondant à #each)]` * Un objet qui répond à `#each` et qui ne transmet que des chaînes de caractères au bloc fourni * Un Fixnum représentant le code statut Avec cela, on peut facilement implémenter un streaming par exemple : ```ruby class Stream def each 100.times { |i| yield "#{i}\n" } end end 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 masques sous forme de chaines de caractères ou des expressions régulières pour définir les routes. Mais il est possible de faire bien plus. Vous pouvez facilement définir vos propres masques : ```ruby class MasqueToutSauf Masque = Struct.new(:captures) def initialize(except) @except = except @captures = Masque.new([]) end def match(str) @caputres unless @except === str end end def tout_sauf(masque) MasqueToutSauf.new(masque) end get tout_sauf("/index") do # ... end ``` Notez que l'exemple ci-dessus est bien trop compliqué et que le même résultat peut être obtenu avec : ```ruby get // do pass if request.path_info == "/index" # ... end ``` Ou bien en utilisant la forme négative : ```ruby get %r{^(?!/index$)} do # ... end ``` ## Fichiers statiques Les fichiers du dossier `./public` sont servis de façon statique. Vous avez la possibilité d'utiliser un autre répertoire en définissant le paramètre `:public_folder` : ```ruby set :public_folder, File.dirname(__FILE__) + '/statique' ``` Notez que le nom du dossier public n'apparait pas dans l'URL. Le fichier `./public/css/style.css` sera appelé via l'URL : `http://exemple.com/css/style.css`. Utilisez le paramètre `:static_cache_control` pour ajouter l'information d'en-tête Cache-Control (voir plus loin). ## Vues / Templates Chaqie langage de template est disponible via sa propre méthode de rendu, lesquelles renvoient tout simplement une chaîne de caractères. ```ruby get '/' do erb :index end ``` Ceci effectue le rendu de la vue `views/index.erb`. Plutôt que d'utiliser le nom d'un template, vous pouvez directement passer le contenu du template : ```ruby get '/' do code = "<%= Time.now %>" erb code end ``` Les méthodes de templates acceptent un second paramètre, un hash d'options : ```ruby get '/' do erb :index, :layout => :post end ``` Ceci effectuera le rendu de la vue `views/index.erb` en l'intégrant au *layout* `views/post.erb` (les vues Erb sont intégrées par défaut au *layout* `views/layout.erb` quand ce fichier existe). Toute option que Sinatra ne comprend pas sera passée au moteur de rendu : ```ruby get '/' do haml :index, :format => :html5 end ``` Vous pouvez également définir des options par langage de template de façon générale : ```ruby set :haml, :format => html5 get '/' do haml :index end ``` Les options passées à la méthode de rendu prennent le pas sur les options définies au moyen de `set`. Options disponibles : **locals** Liste de variables locales passées au document. Pratique pour les vues partielles. Exemple : `erb "<%= foo %>", :locals => {:foo => "bar"}`. **default_encoding** Encodage de caractères à utiliser en cas d'incertitude. Par défaut, c'est `settings.default_encoding`. **views** Dossier de vues dans lequel chercher les templates. Par défaut `settings.views`. **layout** S'il faut ou non utiliser un +layout+ (+true+ or +false+). Indique le template à utiliser lorsque c'est un symbole. Exemple : `erb :index, :layout => !request.xhr?`. **content_type** Content-Type que le template produit, dépend par défaut du langage de template. **scope** Contexte sous lequel effectuer le rendu du template. Par défaut il s'agit de l'instance de l'application. Si vous changez cela, les variables d'instance et les méthodes utilitaires ne seront pas disponibles. **layout_engine** Moteur de rendu à utiliser pour le +layout+. Utile pour les langages ne supportant pas les +layouts+. Il s'agit par défaut du moteur utilisé pour le rendu du template. Exemple : `set :rdoc, :layout_engine => :erb` Les templates sont supposés se trouver directement dans le dossier `./views`. Pour utiliser un dossier de vues différent : ```ruby set :views, settings.root + '/templates' ``` 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 `:'sous_repertoire/template'`). 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 devez simplement l'avoir chargée au préalable : ```ruby require 'rdiscount' # ou require 'bluecloth' get('/') { markdown :index } ``` ### Templates Haml
Dépendances haml
Extensions de fichier .haml
Exemple haml :index, :format => :html5
### Templates Erb
Dépendances erubis ou erb (inclus avec Ruby)
Extensions de fichier .erb, .rhtml ou .erubis (Erubis seulement)
Exemple erb :index
### Templates Builder
Dépendances builder
Extensions de fichier .builder
Exemple builder { |xml| xml.em "salut" }
Ce moteur accepte également un bloc pour des templates en ligne (voir exemple). ### Templates Nokogiri
Dépendances nokogiri
Extensions de fichier .nokogiri
Exemple nokogiri { |xml| xml.em "salut" }
Ce moteur accepte également un bloc pour des templates en ligne (voir exemple). ### Templates Sass
Dépendances sass
Extensions de fichier .sass
Exemple sass :stylesheet, :style => :expanded
### Templates SCSS
Dépendances sass
Extensions de fichier .scss
Exemple scss :stylesheet, :style => :expanded

### Templates Less
Dépendances less
Extensions de fichier .less
Exemple less :stylesheet
### Templates Liquid
Dépendances liquid
Extensions de fichier .liquid
Exemple liquid :index, :locals => { :key => 'value' }
Comme vous ne pouvez appeler de méthodes Ruby (autres que `yield`) dans un template Liquid, vous aurez sûrement à lui passer des variables locales. ### Templates Markdown

Dépendances

rdiscount, redcarpet, bluecloth, kramdown *ou* maruku
Extensions de fichier .markdown, .mkd et .md
Exemple markdown :index, :layout_engine => :erb
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 : ```ruby erb :overview, :locals => { :text => markdown(:introduction) } ``` Notez que vous pouvez également appeler la méthode `markdown` au sein d’autres templates : ```ruby %h1 Hello From Haml ! %p= markdown(:greetings) ``` 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 moteur de rendu différent pour le template et pour le layout en utilisant l’option `:layout_engine`. ### Templates Textile
Dépendances RedCloth
Extensions de fichier .textile
Exemple textile :index, :layout_engine => :erb
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 : ```ruby erb :overview, :locals => { :text => textile(:introduction) } ``` Notez que vous pouvez également appeler la méthode `textile` au sein d’autres templates : ```ruby %h1 Hello From Haml ! %p= textile(:greetings) ``` 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 moteur de rendu différent pour le template et pour le layout en utilisant l’option `:layout_engine`. ### Templates RDoc
Dépendances rdoc
Extensions de fichier .rdoc
Exemple rdoc :README, :layout_engine => :erb
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 : ```ruby erb :overview, :locals => { :text => rdoc(:introduction) } ``` Notez que vous pouvez également appeler la méthode `rdoc` au sein d’autres templates : ```ruby %h1 Hello From Haml ! %p= rdoc(:greetings) ``` 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 moteur de rendu différent pour le template et pour le layout en utilisant l’option `:layout_engine`. ### Templates Radius
Dépendances radius
Extensions de fichier .radius
Exemple radius :index, :locals => { :key => 'value' }
Comme vous ne pouvez pas appeler de méthodes Ruby depuis un template Radius, vous aurez sûrement à lui passer des variables locales. ### Templates Markaby
Dépendances markaby
Extensions de fichier .mab
Exemple markaby { h1 "Bienvenue !" }
Ce moteur accepte également un bloc pour des templates en ligne (voir exemple). ### Templates RABL
Dépendances rabl
Extensions de fichier .rabl
Exemple rabl :index
### Templates Slim
Dépendances slim
Extensions de fichier .slim
Exemple slim :index
### Templates Creole
Dépendances creole
Extensions de fichier .creole
Exemple creole :wiki, :layout_engine => :erb
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 : ```ruby erb :overview, :locals => { :text => markdown(:introduction) } ``` Notez que vous pouvez également appeler la méthode +markdown+ au sein d'autres templates : ```ruby %h1 Hello From Haml ! %p= markdown(:greetings) ``` 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 moteur de rendu différent pour le template et pour le +layout+ en utilisant l'option `:layout_engine`. ### Templates CoffeeScript
Dépendances coffee-script et un [moyen d'exécuter javascript](https://github.com/sstephenson/execjs/blob/master/README.md#readme)
Extensions de fichier .coffee
Exemple coffee :index
### Templates Yajl
Dépendances yajl-ruby
Extensions de fichier .yajl
Exemple yajl :index, :locals => { :key => 'qux' }, :callback => 'present', :variable => 'resource'

Le source du template est évalué en tant que chaine Ruby, puis la variable json obtenue est convertie avec #to_json. ```ruby json = { :foo => 'bar' } json[:baz] = key ``` Les options `:callback` et `:variable` peuvent être utilisées pour décorer l’objet retourné. ``` var resource = {"foo":"bar","baz":"qux"}; present(resource); ``` ### Templates WLang
Dependency wlang
File Extensions .wlang
Example wlang :index, :locals => { :key => 'value' }
L’appel de code ruby au sein des templates n’est pas idiomatique en wlang. L’écriture de templates sans logique est encouragé, via le passage de variables locales. Il est néanmoins possible d’écrire un layout en wlang et d’y utiliser `yield`. ### Templates embarqués ```ruby get '/' do haml '%div.title Bonjour le monde' end ``` Générera le code du template spécifié dans la chaîne de caractères. ### Accéder aux variables dans un Template Un template est évalué dans le même contexte que l'endroit d'où il a été appelé (gestionnaire de route). Les variables d'instance déclarées dans le gestionnaire de route sont directement accessibles dans le template : ```ruby get '/:id' do @foo = Foo.find(params[:id]) haml '%h1= @foo.nom' end ``` Alternativement, on peut passer un hash contenant des variables locales : ```ruby get '/:id' do foo = Foo.find(params[:id]) haml '%h1= foo.nom', :locals => { :foo => foo } end ``` Ceci est généralement utilisé lorsque l'on veut utiliser un template comme partiel (depuis un autre template) et qu'il est donc nécessaire d'adapter les noms de variables. ### Templates dans le fichier source Des templates peuvent être définis dans le fichier source comme ceci : ```ruby require 'sinatra' get '/' do haml :index end __END__ @@ layout %html = yield @@ index %div.title Bonjour le monde ! ``` NOTE : Les templates du fichier source qui contient `require 'sinatra'` sont automatiquement chargés. Si vous avez des templates dans d'autres fichiers source, il faut explicitement les déclarer avec `enable :inline_templates`. ### Templates nommés Les templates peuvent aussi être définis grâce à la méthode de haut niveau `template` : ```ruby template :layout do "%html\n =yield\n" end template :index do '%div.title Bonjour le monde !' end get '/' do haml :index end ``` Si un template nommé "layout" existe, il sera utilisé à chaque fois qu'un template sera affiché. Vous pouvez désactivez les layouts au cas par cas en passant `:layout => false` ou bien les désactiver par défaut au moyen de `set :haml, :layout => false` : ```ruby get '/' do haml :index, :layout => !request.xhr? end ``` ### Associer des extensions de fichier Pour associer une extension de fichier avec un moteur de rendu, utilisez `Tilt.register`. Par exemple, si vous désirez utiliser l'extension de fichier `tt` pour les templates Textile, vous pouvez faire comme suit : ```ruby Tilt.register :tt, Tilt[:textile] ``` ### Ajouter son propre moteur de rendu En premier lieu, déclarez votre moteur de rendu avec Tilt, ensuite créez votre méthode de rendu : ```ruby Tilt.register :monmoteur, MonMerveilleurMoteurDeRendu helpers do def monmoteur(*args) render(:monmoteur, *args) end end get '/' do monmoteur :index end ``` Utilisera `./views/index.monmoteur`. Voir [le dépôt Github](https://github.com/rtomayko/tilt) pour en savoir plus sur Tilt. ## Filtres Les filtres before sont exécutés avant chaque requête, dans le même contexte que les routes, et permettent de modifier la requête et sa réponse. Les variables d'instance déclarées dans les filtres sont accessibles au niveau des routes et des templates : ```ruby before do @note = 'Coucou !' request.path_info = '/foo/bar/baz' end get '/foo/*' do @note #=> 'Coucou !' params[:splat] #=> 'bar/baz' end ``` Les filtres after sont exécutés après chaque requête à l'intérieur du même contexte et permettent de modifier la requête et sa réponse. Les variables d'instance déclarées dans les filtres before ou les routes sont accessibles au niveau des filtres after : ```ruby after do puts response.status end ``` Note : Le corps de la réponse n'est pas disponible au niveau du filtre after car il ne sera généré que plus tard (sauf dans le cas où vous utilisez la méthode +body+ au lieu de simplement renvoyer une chaine depuis vos routes). Les filtres peuvent être associés à un masque, ce qui permet de limiter leur exécution aux cas où la requête correspond à ce masque : ```ruby before '/secret/*' do authentification! end after '/faire/:travail' do |travail| session[:dernier_travail] = travail end ``` Tout comme les routes, les filtres acceptent également des conditions : ```ruby before :agent => /Songbird/ do # ... end after '/blog/*', :host_name => 'example.com' do # ... end ``` ## Helpers Utilisez la méthode de haut niveau `helpers` pour définir des routines qui seront accessibles dans vos gestionnaires de route et dans vos templates : ```ruby helpers do def bar(nom) "#{nom}bar" end end get '/:nom' do bar(params[:nom]) end ``` Vous pouvez aussi définir les méthodes helper dans un module séparé : ```ruby module FooUtils def foo(nom) "#{nom}foo" end end module BarUtils def bar(nom) "#{nom}bar" end end helpers FooUtils, BarUtils ``` Cela a le même résultat que d'inclure les modules dans la classe de l'application. ### Utiliser les sessions Une session est utilisée pour conserver un état entre les requêtes. Une fois activées, vous avez un +hash+ de session par session utilisateur : ```ruby enable :sessions get '/' do "valeur = " << session[:valeur].inspect end get '/:value' do session[:valeur] = params[:valeur] end ``` Notez que enable :sessions enregistre en fait toutes les données dans un +cookie+. Ce n'est pas toujours ce que vous voulez (enregistrer beaucoup de données va augmenter le traffic par exemple). Vous pouvez utiliser n'importe quel +middleware+ Rack de session afin d'éviter cela. N'utiliser *pas* enable :sessions dans ce cas mais charger le +middleware+ de votre choix comme vous le feriez pour n'importe quel autre +middleware+ : ```ruby use Rack::Session::Pool, :expire_after => 2592000 get '/' do "valeur = " << session[:valeur].inspect end get '/:value' do session[:valeur] = params[:valeur] end ``` Pour renforcer la sécurité, les données de session dans le cookie sont signées avec une clé secrète de session. Une clé secrète est générée pour vous au hasard par Sinatra. Toutefois, comme cette clé change à chaque démarrage de votre application, vous pouvez définir cette clé vous-même afin que toutes les instances de votre application la partage : ```ruby set :session_secret, 'super secret' ``` Si vous souhaitez avoir plus de contrôle, vous pouvez également enregistrer un +hash+ avec des options lors de la configuration de `sessions` : ```ruby set :sessions, :domain => 'foo.com' ``` ### Halt Pour arrêter immédiatement la requête dans un filtre ou un gestionnaire de route : ```ruby halt ``` Vous pouvez aussi passer le code retour ... ```ruby halt 410 ``` Ou le texte ... ```ruby halt 'Ceci est le texte' ``` Ou les deux ... ```ruby halt 401, 'Partez !' ``` Ainsi que les entêtes ... ```ruby halt 402, {'Content-Type' => 'text/plain'}, 'revanche' ``` Bien sûr il est possible de combiner un template avec `halt` : ```ruby halt erb(:erreur) ``` ### Passer Une route peut passer le relais aux autres routes qui correspondent également avec `pass` : ```ruby get '/devine/:qui' do pass unless params[:qui] == 'Frank' "Tu m'as eu !" end get '/devine/*' do 'Manqué !' end ``` On sort donc immédiatement de ce gestionnaire et on continue à chercher, dans les masques suivants, le prochain qui correspond à la requête. Si aucun des masques suivants ne correspond, un code 404 est retourné. ### Déclencher une autre route Parfois, +pass+ n'est pas ce que vous recherchez, au lieu de cela vous souhaitez obtenir le résultat d'une autre route. Pour cela, utilisez simplement call : ```ruby get '/foo' do status, headers, body = call env.merge("PATH_INFO" => '/bar') [status, headers, body.map(&:upcase)] end get '/bar' do "bar" end ``` Notez que dans l'exemple ci-dessus, vous faciliterez les tests et améliorerez la performance en déplaçant simplement `"bar"` dans un helper utilisé à la fois par `/foo` et `/bar`. Si vous souhiatez que la requête soit envoyée à la même instance de l'application plutôt qu'à une copie, utilisez `call!` au lieu de `call`. Lisez la spécification Rack si vous souhaitez en savoir plus sur `call`. ### Définir le corps, le code retour et les entêtes Il est possible et recommandé de définir le code retour et le corps de la réponse au moyen de la valeur de retour d'un bloc définissant une route. Quoiqu'il en soit, dans certains cas vous pourriez avoir besoin de définir le coprs de la réponse à un moment arbitraire de l'exécution. Vous pouvez le faire au moyen de la méthode +body+. Si vous faites ainsi, vous pouvez alors utiliser cette même méthode pour accéder au corps de la réponse : ```ruby get '/foo' do body "bar" end after do puts body end ``` Il est également possible de passer un bloc à `body`, qui sera exécuté par le gestionnaire Rack (ceci peut être utilisé pour implémenter un streaming, voir "Valeurs de retour"). Pareillement au corps de la réponse, vous pouvez également définir le code retour et les entêtes : ```ruby get '/foo' do status 418 headers \ "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 : ```ruby 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. Le streaming ne fonctionne pas du tout avec Shotgun. 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 : ```ruby 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 instance de +logger+ : ```ruby get '/' do logger.info "chargement des données" # ... end ``` Ce logger va automatiquement prendre en compte les paramètres de configuration pour la journalisation de votre gestionnaire Rack. Si la journalisation est désactivée, cette méthode renverra un objet factice et vous n'avez pas à vous en inquiéter dans vos routes en le filtrant. Notez que la journalisation est seulement activée par défaut pour `Sinatra::Application`, donc si vous héritez de `>Sinatra::Base`, vous aurez à l'activer vous-même : ```ruby class MonApp < Sinatra::Base configure :production, :development do enable :logging end end ``` Si vous souhaitez utiliser votre propre logger, vous devez définir le paramètre `logging` à `nil` pour être certain qu'aucun middleware de logging ne sera installé (notez toutefois que +logger+ renverra alors +nil+). Dans ce cas, Sinatra utilisera ce qui sera présent dans `env['rack.logger']`. ### Types Mime Quand vous utilisez `send_file` ou des fichiers statiques, vous pouvez rencontrer des types mime que Sinatra ne connaît pas. Utilisez `mime_type` pour les déclarer par extension de fichier : ```ruby configure do mime_type :foo, 'text/foo' end ``` Vous pouvez également les utiliser avec la méthode `content_type` : ```ruby get '/' do content_type :foo "foo foo foo" end ``` ### Former des URLs Pour former des URLs, vous devriez utiliser la méthode +url+, par exemple en Haml : ```ruby %a{:href => url('/foo')} foo ``` Cela prend en compte les proxy inverse et les routeurs Rack, s'ils existent. Cette méthode est également disponible sous l'alias +to+ (voir ci-dessous pour un exemple). ### Redirection du navigateur Vous pouvez déclencher une redirection du navigateur avec la méthode `redirect` : ```ruby get '/foo' do redirect to('/bar') end ``` Tout paramètre additionnel est géré comme des arguments pour la méthode `halt` : ```ruby redirect to('/bar'), 303 redirect 'http://google.com', 'mauvais endroit mon pote' ``` Vous pouvez aussi rediriger vers la page dont l'utilisateur venait au moyen de `redirect back` : ```ruby get '/foo' do "faire quelque chose" end get '/bar' do faire_quelque_chose redirect back end ``` Pour passer des arguments à une redirection, ajoutez-les soit à la requête : ```ruby redirect to('/bar?sum=42') ``` Ou bien utilisez une session : ```ruby enable :sessions get '/foo' do session[:secret] = 'foo' redirect to('/bar') end get '/bar' do session[:secret] end ``` ### Contrôle du cache Définir correctement vos entêtes à la base pour un bon cache HTTP. Vous pouvez facilement définir l'entête Cache-Control de la manière suivante : ```ruby get '/' do cache_control :public "met le en cache !" end ``` Conseil de pro : définir le cache dans un filtre +before+ : ```ruby before do cache_control :public, :must_revalidate, :max_age => 60 end ``` Si vous utilisez la méthode +expires+ pour définir l'entête correspondant, `Cache-Control` sera alors défini automatiquement : ```ruby before do expires 500, :public, :must_revalidate end ``` 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 : ```ruby get '/article/:id' do @article = Article.find params[:id] last_modified @article.updated_at etag @article.sha1 erb :article end ``` Il est également possible d'utiliser un [weak ETag](http://en.wikipedia.org/wiki/HTTP_ETag#Strong_and_weak_validation) : ```ruby etag @article.sha1, :weak ``` 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 pour un reverse-proxy de cache, essayez [rack-cache](https://github.com/rtomayko/rack-cache) : ```ruby require "rack/cache" require "sinatra" use Rack::Cache get '/' do cache_control :public, :max_age => 36000 sleep 5 "hello" end ``` Utilisez le paramètre `:static_cache_control` pour ajouter l'information d'en-tête `Cache-Control` (voir plus loin). D'après la RFC 2616, votre application devrait se comporter différement lorsque l'en-tête If-Match ou If-None-Match est défini à `*` en tenant compte du fait que la resource demandée existe déjà ou pas. Sinatra considère que les requêtes portant sur des resources sûres (tel que get) ou idempotentes (tel que put) existent déjà et pour les autres resources (par exemple dans le cas de requêtes post) qu'il s'agit de nouvelles resources. Vous pouvez modifier ce comportement en passant une option `:new_resource` : ```ruby get '/create' do etag '', :new_resource => true Article.create erb :new_article end ``` Si vous souhaitez utilisez un ETag faible, utilisez l'option :kind : ``` etag '', :new_resource => true, :kind => :weak ``` ### Envoyer des fichiers Pour envoyer des fichiers, vous pouvez utiliser la méthode `send_file` : ```ruby get '/' do send_file 'foo.png' end ``` Quelques options sont également acceptées : ```ruby send_file 'foo.png', :type => :jpg ``` Les options sont :
filename
le nom du fichier dans la réponse, par défaut le nom du fichier envoyé.
last_modified
valeur pour l’entête Last-Modified, par défaut la date de modification du fichier
type
type de contenu à utiliser, deviné à partir de l’extension de fichier si absent
disposition
utilisé pour Content-Disposition, les valuers possibles étant : `nil` (par défaut), `:attachment` et `:inline`
length
entête Content-Length, par défaut la taille du fichier
status
code état à renvoyer. Utile quand un fichier statique sert de page d’erreur.
Si le gestionnaire Rack le supporte, d'autres moyens que le +streaming+ via le processus Ruby seront utilisés. Si vous utilisez cette méthode, Sinatra gérera automatiquement les requêtes de type +range+. ### Accéder à l'objet requête L'objet correspondant à la requête envoyée peut être récupéré dans le contexte de la requête (filtres, routes, gestionnaires d'erreur) au moyen de la méthode +request+ : ```ruby # application tournant à l'adresse http://exemple.com/exemple 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 # corps de la requête envoyée par le client # (voir ci-dessous) request.scheme # "http" request.script_name # "/exemple" request.path_info # "/foo" request.port # 80 request.request_method # "GET" request.query_string # "" request.content_length # taille de request.body request.media_type # type de média pour request.body request.host # "exemple.com" request.get? # true (méthodes similaires pour les autres # verbes HTTP) request.form_data? # false request["UN_ENTETE"] # valeur de l'entête UN_ENTETE request.referer # référant du client ou '/' request.user_agent # user agent (utilisé par la condition :agent) request.cookies # tableau contenant les cookies du navigateur request.xhr? # requête AJAX ? request.url # "http://exemple.com/exemple/foo" request.path # "/exemple/foo" request.ip # adresse IP du client request.secure? # false request.forwarded? # vrai (si on est derrière un proxy inverse) request.env # tableau brut de l'environnement fourni par # Rack end ``` Certaines options, telles que `script_name` ou `path_info` peuvent également être modifiées : ```ruby before { request.path_info = "/" } get "/" do "toutes les requêtes arrivent ici" end ``` `request.body` est un objet IO ou StringIO : ```ruby post "/api" do request.body.rewind # au cas où il a déjà été lu donnees = JSON.parse request.body.read "Bonjour #{donnees['nom']} !" end ``` ### Fichiers joints Vous pouvez utiliser la méthode +attachment+ pour indiquer au navigateur que la réponse devrait être stockée sur le disque plutôt qu'affichée : ```ruby get '/' do attachment "enregistre-le !" end ``` Vous pouvez également lui passer un nom de fichier : ```ruby get '/' do attachment "info.txt" "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 : ```ruby 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 : ```ruby helpers do def time_for(value) case value when :yesterday then Time.now - 24*60*60 when :tomorrow then Time.now + 24*60*60 else super end end end get '/' do last_modified :yesterday expires :tomorrow "salut" end ``` ### Chercher les fichiers de templates La méthode `find_template` est utilisée pour trouver les fichiers de templates à générer : ```ruby find_template settings.views, 'foo', Tilt[:haml] do |file| puts "pourrait être #{file}" end ``` Ce n'est pas très utilise. En revanche, il est utile de pouvoir surcharger cette méthode afin de définir son propre mécanisme de recherche. Par exemple, vous pouvez utiliser plus d'un répertoire de vues : ```ruby set :views, ['views', 'templates'] helpers do def find_template(views, name, engine, &block) Array(views).each { |v| super(v, name, engine, &block) } end end ``` Un autre exemple est d'utiliser des répertoires différents pour des moteurs de rendu différents : ```ruby set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views' helpers do def find_template(views, name, engine, &block) _, folder = views.detect { |k,v| engine == Tilt[k] } folder ||= views[:default] super(folder, name, engine, &block) end end ``` Vous pouvez également écrire cela dans une extension et la partager avec d'autres ! Notez que `find_template` ne vérifie pas que le fichier existe mais va plutôt exécuter le bloc pour tous les chemins possibles. Cela n'induit pas un problème de performance dans le sens où `render` va utiliser +break+ dès qu'un fichier est trouvé. De plus, l'emplacement des templates (et leur contenu) est mis en cache si vous n'êtes pas en mode développement. Vous devriez garder cela en tête si vous écrivez une méthode vraiment dingue. ## Configuration Lancé une seule fois au démarrage de tous les environnements : ```ruby configure do # définir un paramètre set :option, 'value' # définir plusieurs paramètre set :a => 1, :b => 2 # identique à "set :option, true" enable :option # identique à "set :option, false"" disable :option # vous pouvez également avoir des paramètres dynamiques avec des blocs set(:css_dir) { File.join(views, 'css') } end ``` Lancé si l'environnement (variable d'environnement RACK_ENV) est défini comme `:production` : configure :production do ... end Lancé si l'environnement est `:production` ou `:test` : configure :production, :test do ... end Vous pouvez accéder à ces paramètres via `settings` : ``` configure do set :foo, 'bar' end get '/' do settings.foo? # => true settings.foo # => 'bar' ... end ``` ### Se protéger des attaques Sinatra utilise [Rack::Protection](https://github.com/rkh/rack-protection#readme) pour protéger votre application contre les principales attaques opportunistes. Vous pouvez très simplement désactiver cette fonctionnalité (ce qui exposera votre application à beaucoup de vulnerabilités courantes) : ```ruby disable :protection ``` Pour désactiver seulement un type de protection, vous pouvez définir `protection` avec un hash d'options : ```ruby set :protection, :except => :path_traversal ``` Vous pouvez également lui passer un tableau pour désactiver plusieurs types de protection : ```ruby set :protection, :except => [:path_traversal, :session_hijacking] ``` ### Paramètres disponibles
absolute_redirects
Si désactivé, Sinatra permettra les redirections relatives. Toutefois, Sinatra ne sera plus conforme à la RFC 2616 (HTTP 1.1), qui n’autorise que les redirections absolues.

Activez si votre application tourne derrière un proxy inverse qui n’a pas été correctement configuré. Notez que la méthode url continuera de produire des URLs absolues, sauf si vous lui passez false comme second argument.

Désactivé par défaut.

add_charsets

types mime pour lesquels la méthode content_type va automatiquement ajouter l’information du charset.

Vous devriez lui ajouter des valeurs plutôt que de l’écraser :

settings.add_charsets >> "application/foobar"
app_file

chemin pour le fichier de l’application principale, utilisé pour détecter la racine du projet, les dossiers public et vues, et les templates en ligne.

bind
adresse IP sur laquelle se brancher (par défaut : 0.0.0.0). Utiliser seulement pour le serveur intégré.
default_encoding
encodage à utiliser si inconnu (par défaut "utf-8")
dump_errors
afficher les erreurs dans le log.
environment
environnement courant, par défaut ENV['RACK_ENV'], ou "development" si absent.
logging
utiliser le logger.
lock

Place un lock autour de chaque requête, n’exécutant donc qu’une seule requête par processus Ruby.

Activé si votre application n’est pas thread-safe. Désactivé par défaut.

method_override
utilise la magie de _method afin de permettre des formulaires put/delete dans des navigateurs qui ne le permettent pas.
port
port à écouter. Utiliser seulement pour le serveur intégré.
prefixed_redirects
si oui ou non request.script_name doit être inséré dans les redirections si un chemin non absolu est utilisé. Ainsi, redirect '/foo' se comportera comme redirect to('/foo'). Désactivé par défaut.
protection
défini s’il faut activer ou non la protection contre les attaques web. Voir la section protection précédente.
public_dir
alias pour public_folder. Voir ci-dessous.
public_folder
chemin pour le dossier à partir duquel les fichiers publics sont servis. Utilisé seulement si les fichiers statiques doivent être servis (voir le paramètre static). Si non défini, il découle du paramètre app_file.
reload_templates
si oui ou non les templates doivent être rechargés entre les requêtes. Activé en mode développement.
root
chemin pour le dossier racine du projet. Si non défini, il découle du paramètre app_file.
raise_errors
soulever les erreurs (ce qui arrêtera l’application). Désactivé par défaut sauf lorsque environment est défini à "test".
run
si activé, Sinatra s’occupera de démarrer le serveur, ne pas activer si vous utiliser rackup ou autres.
running
est-ce que le serveur intégré est en marche ? ne changez pas ce paramètre !
server
serveur ou liste de serveurs à utiliser pour le serveur intégré. Par défaut [‘thin’, ‘mongrel’, ‘webrick’], l’ordre indiquant la priorité.
sessions
active le support des sessions basées sur les cookies, en utilisant Rack::Session::Cookie. Reportez-vous à la section ‘Utiliser les sessions’ pour plus d’informations.
show_exceptions
affiche la trace de l’erreur dans le navigateur lorsqu’une exception se produit. Désactivé par défaut sauf lorsque environment est défini à "development".
static
Si oui ou non Sinatra doit s’occuper de servir les fichiers statiques. Désactivez si vous utilisez un serveur capable de le gérer lui même. Le désactiver augmentera la performance. Activé par défaut pour le style classique, désactivé pour le style modulaire.
static_cache_control
A définir quand Sinatra rend des fichiers statiques pour ajouter les en-têtes Cache-Control. Utilise le helper cache_control. Désactivé par défaut. Utiliser un array explicite pour définir des plusieurs valeurs : set :static_cache_control, [:public, :max_age => 300]
threaded
à définir à true pour indiquer à Thin d’utiliser EventMachine.defer pour traiter la requête.
views
chemin pour le dossier des vues. Si non défini, il découle du paramètre app_file.
## Environements Il existe trois environnements prédéfinis : `"development"`, `"production"` et `"test"`. Les environements peuvent être sélectionné via la variable d'environnement +RACK_ENV+. Sa valeur par défaut est `"development"`. Dans ce mode, tous les templates sont rechargés à chaque requête. Des handlers spécifiques pour `not_found` et `error` sont installés pour vous permettre d'avoir une pile de trace dans votre navigateur. En mode `"production"` et `"test"` les templates sont mis en cache par défaut. Pour exécuter votre application dans un environnement différent, utilisez l'option `-e` de Ruby : ```bash $ ruby mon_application.rb -e [ENVIRONMENT] ``` Vous pouvez utiliser une des méthodes +development?+, +test?+ et +production?+ pour déterminer quel est l'environnement en cours. ## Gérer les erreurs Les gestionnaires d'erreur s'exécutent dans le même contexte que les routes ou les filtres, ce qui veut dire que vous avez accès (entre autres) aux bons vieux `haml`, `erb`, `halt`, etc. ### NotFound Quand une exception Sinatra::NotFound est soulevée, ou que le code retour est 404, le gestionnaire not_found est invoqué : ```ruby not_found do 'Pas moyen de trouver ce que vous cherchez' end ``` ### Error Le gestionnaire +error+ est invoqué à chaque fois qu'une exception est soulevée dans une route ou un filtre. L'objet exception est accessible via la variable Rack `sinatra.error` : ```ruby error do 'Désolé mais une méchante erreur est survenue - ' + env['sinatra.error'].name end ``` Erreur sur mesure : ```ruby error MonErreurSurMesure do 'Donc il est arrivé ceci...' + env['sinatra.error'].message end ``` Donc si ceci arrive : ```ruby get '/' do raise MonErreurSurMesure, 'quelque chose de mal' end ``` Vous obtenez ça : Donc il est arrivé ceci... quelque chose de mal Alternativement, vous pouvez avoir un gestionnaire d'erreur associé à un code particulier : ```ruby error 403 do 'Accès interdit' end get '/secret' do 403 end ``` Ou un intervalle : ```ruby error 400..510 do 'Boom' end ``` Sinatra installe pour vous quelques gestionnaires `not_found` et `error` génériques lorsque vous êtes en environnement `development`. ## Les Middlewares Rack Sinatra tourne avec [Rack](http://rack.rubyforge.org/), une interface standard et minimale pour les web frameworks Ruby. Un des points forts de Rack est le support de ce que l'on appelle des "middlewares" -- composant qui vient se situer entre le serveur et votre application, et dont le but est de visualiser/manipuler la requête/réponse HTTP, et d'offrir diverses fonctionnalités classiques. Sinatra permet de construire facilement des middlewares Rack via la méthode de haut niveau +use+ : ```ruby require 'sinatra' require 'mon_middleware_perso' use Rack::Lint use MonMiddlewarePerso get '/bonjour' do 'Bonjour le monde' end ``` La sémantique de +use+ est identique à celle définie dans le DSL de [Rack::Builder](http://rack.rubyforge.org/doc/classes/Rack/Builder.html) (le plus souvent utilisé dans un fichier rackup). Par exemple, la méthode +use+ accepte divers arguments ainsi que des blocs : ``` use Rack::Auth::Basic do |login, password| login == 'admin' && password == 'secret' end ``` Rack est distribué avec une bonne variété de middlewares standards pour les logs, débuguer, faire du routage URL, de l'authentification, gérer des sessions. Sinatra utilise beaucoup de ces composants automatiquement via la configuration, donc pour ceux-ci vous n'aurez pas à utiliser la méthode `use`. ## Tester Les tests pour Sinatra peuvent être écrit avec n'importe quelle bibliothèque basée sur Rack. [Rack::Test](http://gitrdoc.com/brynary/rack-test) est recommandé : ```ruby require 'mon_application_sinatra' require 'test/unit' require 'rack/test' class MonTest < Test::Unit::TestCase include Rack::Test::Methods def app Sinatra::Application end def test_ma_racine get '/' assert_equal 'Bonjour le monde !', last_response.body end def test_avec_des_parametres get '/rencontrer', :name => 'Frank' assert_equal 'Salut Frank !', last_response.body end def test_avec_rack_env get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' assert_equal "Vous utilisez Songbird !", last_response.body end end ``` ## Sinatra::Base - Les Middlewares, Bibliothèques, et Applications Modulaires Définir votre application au niveau supérieur fonctionne bien dans le cas des micro-applications mais présente pas mal d'inconvénients pour créer des composants réutilisables sous forme de middlewares Rack, de Rails metal, de simples librairies avec un composant serveur ou même d'extensions Sinatra. Le niveau supérieur suppose une configuration dans le style des micro-applications (une application d'un seul fichier, des répertoires `./public` et `./views`, des logs, une page d'erreur, etc...). C'est là que `Sinatra::Base` prend tout son intérêt : ```ruby require 'sinatra/base' class MonApplication < Sinatra::Base set :sessions, true set :foo, 'bar' get '/' do 'Bonjour le monde !' end end ``` Les méthodes de la classe `Sinatra::Base` sont parfaitement identiques à celles disponibles via le DSL de haut niveau. Il suffit de deux modifications pour transformer la plupart des applications de haut niveau en un composant `Sinatra::Base` : * Votre fichier doit charger `sinatra/base` au lieu de `sinatra`, sinon toutes les méthodes du DSL Sinatra seront importées dans l'espace de nom principal. * Les gestionnaires de routes, la gestion d'erreur, les filtres et les options doivent être placés dans une classe héritant de `Sinatra::Base`. `Sinatra::Base` est une page blanche. La plupart des options sont désactivées par défaut, y compris le serveur intégré. Reportez-vous à [Options et Configuration](http://sinatra.github.com/configuration.html) pour plus d'informations sur les options et leur fonctionnement. ### Style modulaire vs. style classique Contrairement aux idées reçues, il n'y a rien de mal à utiliser le style classique. Si c'est ce qui convient pour votre application, vous n'avez pas aucune raison de passer à une application modulaire. Le principal inconvénient du style classique sur le style modulaire est que vous ne pouvez avoir qu'une application Ruby par processus Ruby. Si vous pensez en utiliser plus, passez au style modulaire. Et rien ne vous empêche de mixer style classique et style modulaire. Si vous passez d'un style à l'autre, souvenez-vous des quelques différences mineures en ce qui concerne les paramètres par défaut : Paramètre Classique Modulaire app_file fichier chargeant sinatra fichier héritant de Sinatra::Base run $0 == app_file false logging true false method_override true false inline_templates true false static true false ### Servir une application modulaire Il y a deux façons de faire pour démarrer une application modulaire, démarrez avec `run!` : ```ruby # my_app.rb require 'sinatra/base' class MyApp < Sinatra::Base # ... code de l'application ici ... # démarre le serveur si ce fichier est directement exécuté run! if app_file == $0 end ``` Démarrez ensuite avec : ```bash $ ruby my_app.rb ``` Ou alors avec un fichier `config.ru`, qui permet d'utiliser n'importe quel gestionnaire Rack : ```ruby # config.ru require './my_app' run MyApp ``` Exécutez : ```bash $ rackup -p 4567 ``` ### Utiliser une application de style classique avec un fichier config.ru Ecrivez votre application : ```ruby # app.rb require 'sinatra' get '/' do 'Bonjour le monde !' end ``` Et un fichier `config.ru` correspondant : ```ruby require './app' run Sinatra::Application ``` ### Quand utiliser un fichier config.ru ? Quelques cas où vous devriez utiliser un fichier `config.ru` : * Vous souhaitez déployer avec un autre gestionnaire Rack (Passenger, Unicorn, Heroku, ...). * Vous souhaitez utiliser plus d'une sous-classe de `Sinatra::Base`. * Vous voulez utiliser Sinatra comme un middleware, non en tant que endpoint. **Il n'est pas nécessaire de passer par un fichier `config.ru` pour la seule raison que vous êtes passé au style modulaire, et vous n'avez pas besoin de passer au style modulaire pour utiliser un fichier `config.ru`.** ### Utiliser Sinatra comme Middleware Non seulement Sinatra peut utiliser d'autres middlewares Rack, il peut également être à son tour utilisé au-dessus de n'importe quel endpoint Rack en tant que middleware. Ce endpoint peut très bien être une autre application Sinatra, ou n'importe quelle application basée sur Rack (Rails/Ramaze/Camping/...) : ```ruby require 'sinatra/base' class EcranDeConnexion < Sinatra::Base enable :sessions get('/connexion') { haml :connexion } post('/connexion') do if params[:nom] = 'admin' && params[:motdepasse] = 'admin' session['nom_utilisateur'] = params[:nom] else redirect '/connexion' end end end class MonApp < Sinatra::Base # le middleware sera appelé avant les filtres use EcranDeConnexion before do unless session['nom_utilisateur'] halt "Accès refusé, merci de vous connecter." end end get('/') { "Bonjour #{session['nom_utilisateur']}." } end ``` ### Création dynamique d'applications Il se peut que vous ayez besoin de créer une nouvelle application à l'exécution sans avoir à les assigner à une constante, vous pouvez le faire grâce à `Sinatra.new` : ```ruby require 'sinatra/base' mon_app = Sinatra.new { get('/') { "salut" } } mon_app.run! ``` L'application dont elle hérite peut être passé en argument optionnel : ```ruby # config.ru require 'sinatra/base' controleur = Sinatra.new do enable :logging helpers MyHelpers end map('/a') do run Sinatra.new(controleur) { get('/') { 'a' } } end map('/b') do run Sinatra.new(controleur) { get('/') { 'b' } } end ``` C'est notamment utile pour tester des extensions à Sinatra ou bien pour utiliser Sinatra dans votre propre bibliothèque. Cela permet également d'utiliser très facilement Sinatra comme middleware : ```ruby require 'sinatra/base' use Sinatra do get('/') { ... } end run RailsProject::Application ``` ## Contextes et Binding Le contexte dans lequel vous êtes détermine les méthodes et variables disponibles. ### Contexte de l'application/classe Toute application Sinatra correspond à une sous-classe de `Sinatra::Base`. Si vous utilisez le DSL haut niveau (`require 'sinatra'`), alors cette classe est `Sinatra::Application`, sinon il s'agit de la sous-classe que vous avez définie. Dans le contexte de la classe, vous avez accès aux méthodes telles que `get` ou `before`, mais vous n'avez pas accès aux objets +request+ ou +session+ car c'est la même classe d'application qui traitera toutes les requêtes. Les options définies au moyen de +set+ deviennent des méthodes de classe : ```ruby class MonApp < Sinatra::Base # Eh, je suis dans le contexte de l'application ! set :foo, 42 foo # => 42 get '/foo' do # Eh, je ne suis plus dans le contexte de l'application ! end end ``` Vous avez le binding du contexte de l'application dans : * Le corps de la classe d'application * Les méthodes définies par les extensions * Le bloc passé à `helpers` * Les procs/blocs utilisés comme argument pour `set` * Le bloc passé à `Sinatra.new` Vous pouvez atteindre ce contexte (donc la classe) de la façon suivante : * Via l'objet passé dans les blocs `configure` (`configure { |c| ... }`) * En utilisant `settings` dans le contexte de la requête ### Contexte de la requête/instance Pour tout traitement d'une requête, une nouvelle instance de votre classe d'application est créée et tous vos gestionnaires sont exécutés dans ce contexte. Dans ce dernier, vous pouvez accéder aux objets `request` et `session` et faire appel aux fonctions de rendu telles que `erb` ou `haml`. Vous pouvez accéder au contexte de l'application depuis le contexte de la requête au moyen de `settings` : ```ruby class MonApp < Sinatra::Base # Eh, je suis dans le contexte de l'application ! get '/ajouter_route/:nom' do # Contexte de la requête pour '/ajouter_route/:nom' @value = 42 settings.get("/#{params[:nom]}") do # Contexte de la requête pour "/#{params[:nom]}" @value # => nil (on est pas au sein de la même requête) end "Route ajoutée !" end end ``` Vous avez le binding du contexte de la requête dans : * les blocs get/head/post/put/delete/options * les filtres before/after * les méthodes utilitaires (définies au moyen de `helpers`) * les vues/templates ### Le contexte de délégation Le contexte de délégation se contente de transmettre les appels de méthodes au contexte de classe. Toutefois, il ne se comporte pas à 100% comme le contexte de classe car vous n'avez pas le binding de la classe : seules les méthodes spécifiquement déclarées pour délégation sont disponibles et il n'est pas possible de partager des variables/états avec le contexte de classe (comprenez : `self` n'est pas le même). Vous pouvez ajouter des délégation de méthodes en appelant `Sinatra::Delegator.delegate :method_name`. Vous avez le binding du contexte de délégation dans : * Le binding de haut niveau, si vous avez utilisé `require "sinatra"` * Un objet qui inclut le module `Sinatra::Delegator` Jetez un oeil pour vous faire une idée : voici le [mixin Sinatra::Delegator](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/base.rb#L1609-1633) qui [étend l'objet principal](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/main.rb#L28-30). ## Ligne de commande Les applications Sinatra peuvent être lancées directement : ```ruby $ ruby mon_application.rb [-h] [-x] [-e ENVIRONNEMENT] [-p PORT] [-o HOTE] [-s SERVEUR] ``` Les options sont : ``` -h # aide -p # déclare le port (4567 par défaut) -o # déclare l'hôte (0.0.0.0 par défaut) -e # déclare l'environnement (+development+ par défaut) -s # déclare le serveur/gestionnaire à utiliser (thin par défaut) -x # active le mutex lock (off par défaut) ``` ## Configuration nécessaire Les versions suivantes de Ruby sont officiellement supportées :
Ruby 1.8.7
1.8.7 est complètement supporté, toutefois si rien ne vous en empêche, nous vous recommandons de passer à 1.9.2 ou bien de passer à JRuby ou Rubinius. Le support de Ruby 1.8.7 ne sera pas supprimé avant la sortie de Sinatra 2.0 et de Ruby 2.0, à moins qu’un improbable Ruby 1.8.8 apparaisse. Et même dans ce cas, nous pourrions continuer à le supporter. **Ruby 1.8.6 n’est plus supporté**. Si vous souhaitez utiliser la version 1.8.6, vous devez revenir à Sinatra 1.2 qui continuera à recevoir des corrections de bugs tant que Sinatra 1.4.0 ne sera pas livré.
Ruby 1.9.2
1.9.2 est totalement supporté et recommandé. N’utilisez pas 1.9.2p0 car il provoque des erreurs de segmentation à l’exécution de Sinatra. Son support continuera au minimum jusqu’à la sortie de Ruby 1.9.4/2.0 et le support de la dernière version 1.9 se poursuivra aussi longtemps que la core team de Ruby la supportera.
Ruby 1.9.3
1.9.3 est totalement supporté et recommandé. Nous vous rappelons que passer à 1.9.3 depuis une version précédente annulera toutes les sessions.
Rubinius
Rubinius est officiellement supporté (Rubinius <= 1.2.4), tout fonctionne, y compris tous les langages de template. La version 2.0 à venir est également supportée.
JRuby
JRuby est officiellement supporté (JRuby <= 1.6.5). Aucune anomalie avec des bibliothèques de templates tierces ne sont connues. Toutefois, si vous choisissez JRuby, alors tournez vous vers des gestionnaires Rack JRuby car le serveur Thin n’est pas complètement supporté par JRuby. Le support des extensions C dans JRuby est encore expérimental, ce qui n’affecte que RDiscount, Redcarpet and RedCloth pour l’instant.
Nous gardons également un oeil sur les versions Ruby à venir. Les implémentations Ruby suivantes ne sont pas officiellement supportées mais sont malgré tout connues pour permettre de faire fonctionner Sinatra : * Versions plus anciennes de JRuby et Rubinius * Ruby Enterprise Edition * MacRuby, Maglev, IronRuby * Ruby 1.9.0 et 1.9.1 (mais nous déconseillons leur utilisation) Le fait de ne pas être officiellement supporté signifie que si quelque chose ne fonctionne pas uniquement sur cette plateforme alors c'est un problème de la plateforme et pas un bug de Sinatra. Nous lançons également notre intégration continue (CI) avec ruby-head (la future 2.0.0) et la branche 1.9.4, mais étant donné les évolutions continuelles, nous ne pouvont rien garantir, si ce n'est que les versions 1.9.4p0 et 2.0.0p0 seront supportées. Sinatra devrait fonctionner sur n'importe quel système d'exploitation supportant l'implémentation Ruby choisie. Il n'est pas possible d'utiliser Sinatra sur Cardinal, SmallRuby, Blueuby ou toute version de Ruby antérieure à 1.8.7 à l'heure actuelle. ## Essuyer les plâtres Si vous voulez utiliser la toute dernière version de Sinatra, n'ayez pas peur de faire tourner votre application sur la branche master, cela devrait être stable. Nous publions également une gem de +prerelease+ de temps en temps que vous pouvez installer comme suit : ```ruby $ gem install sinatra --pre ``` afin d'avoir les toutes dernières fonctionnalités. ### Avec Bundler Si vous voulez faire tourner votre application avec le tout dernier Sinatra, [Bundler](http://gembundler.com/) est recommandé. Tout d'abord, installer bundler si vous ne l'avez pas : ```bash $ gem install bundler ``` Ensuite, dans le dossier de votre projet, créez un fichier +Gemfile+ : ```ruby source :rubygems gem 'sinatra', :git => "git://github.com/sinatra/sinatra.git" # autres dépendances gem 'haml' # par exemple, si vous utilisez haml gem 'activerecord', '~> 3.0' # peut-être que vous avez également besoin # de ActiveRecord 3.x ``` Notez que vous aurez à lister toutes les dépendances de votre application dans ce fichier. Les dépendances directes de Sinatra (Rack et Tilt) seront automatiquement téléchargées et ajoutées par Bundler. Maintenant, vous pouvez faire tourner votre application de la façon suivante : ```bash $ bundle exec ruby myapp.rb ``` ### Faites le vous-même Créez un clone local et démarrez votre application avec le dossier `sinatra/lib` dans le `$LOAD_PATH` : ```bash $ cd myapp $ git clone git://github.com/sinatra/sinatra.git $ ruby -Isinatra/lib myapp.rb A l'avenir, pour mettre à jour le code source de Sinatra : ```bash $ cd myapp/sinatra $ git pull ``` ### Installez globalement Vous pouvez construire la gem vous-même : ```bash $ git clone git://github.com/sinatra/sinatra.git $ cd sinatra $ rake sinatra.gemspec $ rake install ``` Si vous installez les gems en tant que +root+, la dernière étape sera : ```bash $ sudo rake install ``` ## Versions Sinatra se conforme aux (versions sémantiques)[http://semver.org/], aussi bien SemVer que SemVerTag. ## Mais encore * [Site internet](http://www.sinatrarb.com/) - Plus de documentation, de news, et des liens vers d'autres ressources. * [Contribuer](http://www.sinatrarb.com/contributing) - Vous avez trouvé un bug ? Besoin d'aide ? Vous avez un patch ? * [Suivi des problèmes](http://github.com/sinatra/sinatra/issues) * [Twitter](http://twitter.com/sinatra) * [Mailing List])(http://groups.google.com/group/sinatrarb/topics) * [IRC : #sinatra](irc://chat.freenode.net/#sinatra) sur http://freenode.net * [IRC : #sinatra](irc://chat.freenode.net/#sinatra) on http://freenode.net * [Sinatra Book](http://sinatra-book.gittr.com) Tutoriels et recettes * [Sinatra Recipes](http://recipes.sinatrarb.com/) trucs et astuces rédigés par la communauté * Documentation API de la [dernière version](http://rubydoc.info/gems/sinatra) ou du [HEAD courant](http://rubydoc.info/github/sinatra/sinatra) sur http://rubydoc.info * [CI server](http://travis-ci.org/sinatra/sinatra) sinatra-1.4.3/README.pt-br.md0000644000004100000410000005206312161612727015525 0ustar www-datawww-data# Sinatra *Atenção: Este documento é apenas uma tradução da versão em inglês e pode estar desatualizado.* Sinatra é uma [DSL](http://pt.wikipedia.org/wiki/Linguagem_de_domínio_específico) para criar aplicações web em Ruby com o mínimo de esforço e rapidez: ``` ruby # minhaapp.rb require 'sinatra' get '/' do 'Olá Mundo!' end ``` Instale a gem e execute o arquivo criado acima: ``` shell gem install sinatra ruby minhaapp.rb ``` Acesse em: [localhost:4567](http://localhost:4567) Recomendamos a execução de `gem install thin`. Caso esta gem esteja disponível, o Sinatra irá utilizá-la. ## Rotas No Sinatra, uma rota é um método HTTP emparelhado com um padrão de URL. Cada rota possui um bloco de execução: ``` ruby get '/' do .. mostrando alguma coisa .. end post '/' do .. criando alguma coisa .. end put '/' do .. atualizando alguma coisa .. end patch '/' do .. modificando alguma coisa .. end delete '/' do .. removendo alguma coisa .. end options '/' do .. estabelecendo alguma coisa .. end ``` As rotas são interpretadas na ordem em que são definidas. A primeira rota encontrada responde ao pedido. Padrões de rota podem conter parâmetros nomeados, acessível através do hash `params`: ``` ruby get '/ola/:nome' do # corresponde a "GET /ola/foo" e "GET /ola/bar" # params[:nome] é 'foo' ou 'bar' "Olá #{params[:nome]}!" end ``` Você também pode acessar parâmetros nomeados através dos parâmetros de um bloco: ``` ruby get '/ola/:nome' do |n| "Olá #{n}!" end ``` Padrões de rota também podem conter parâmetros splat (wildcard), acessível através do array `params[: splat]`: ``` ruby get '/diga/*/para/*' do # corresponde a /diga/ola/para/mundo params[:splat] # => ["ola", "mundo"] end get '/download/*.*' do # corresponde a /download/pasta/do/arquivo.xml params[:splat] # => ["pasta/do/arquivo", "xml"] end ``` Ou com parâmetros de um bloco: ``` ruby get '/download/*.*' do |pasta, ext| [pasta, ext] # => ["pasta/do/arquivo", "xml"] end ``` Rotas podem corresponder com expressões regulares: ``` ruby get %r{/ola/([\w]+)} do "Olá, #{params[:captures].first}!" end ``` Ou com parâmetros de um bloco: ``` ruby get %r{/ola/([\w]+)} do |c| "Olá, #{c}!" end ``` Padrões de rota podem contar com parâmetros opcionais: ``` ruby get '/posts.?:formato?' do # corresponde a "GET /posts" e qualquer extensão "GET /posts.json", "GET /posts.xml", etc. end ``` A propósito, a menos que você desative a proteção contra ataques (veja abaixo), o caminho solicitado pode ser alterado antes de concluir a comparação com as suas rotas. ### Condições Rotas podem incluir uma variedade de condições, tal como o `user agent`: ``` ruby get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do "Você está usando o Songbird versão #{params[:agent][0]}" end get '/foo' do # Correspondente a navegadores que não sejam Songbird end ``` Outras condições disponíveis são `host_name` e `provides`: ``` ruby get '/', :host_name => /^admin\./ do "Área administrativa. Acesso negado!" end get '/', :provides => 'html' do haml :index end get '/', :provides => ['rss', 'atom', 'xml'] do builder :feed end ``` Você pode facilmente definir suas próprias condições: ``` ruby set(:probabilidade) { |valor| condition { rand <= valor } } get '/ganha_um_carro', :probabilidade => 0.1 do "Você ganhou!" end get '/ganha_um_carro' do "Sinto muito, você perdeu." end ``` Use splat, para uma condição que levam vários valores: ``` ruby set(:auth) do |*roles| # <- observe o splat aqui condition do unless logged_in? && roles.any? {|role| current_user.in_role? role } redirect "/login/", 303 end end end get "/minha/conta/", :auth => [:usuario, :administrador] do "Detalhes da sua conta" end get "/apenas/administrador/", :auth => :administrador do "Apenas administradores são permitidos aqui!" end ``` ### Retorno de valores O valor de retorno do bloco de uma rota determina pelo menos o corpo da resposta passado para o cliente HTTP, ou pelo menos o próximo middleware na pilha Rack. Frequentemente, isto é uma `string`, tal como nos exemplos acima. Mas, outros valores também são aceitos. Você pode retornar uma resposta válida ou um objeto para o Rack, sendo eles de qualquer tipo de objeto que queira. Além disto, é possível retornar um código de status HTTP. - Um array com três elementros: `[status (Fixnum), cabecalho (Hash), corpo da resposta (responde à #each)]` - Um array com dois elementros: `[status (Fixnum), corpo da resposta (responde à #each)]` - Um objeto que responda à `#each` sem passar nada, mas, sim, `strings` para um dado bloco - Um objeto `Fixnum` representando o código de status Dessa forma, podemos implementar facilmente um exemplo de streaming: ``` ruby class Stream def each 100.times { |i| yield "#{i}\n" } end end get('/') { Stream.new } ``` Você também pode usar o método auxiliar `stream` (descrito abaixo) para incorporar a lógica de streaming na rota. ### Custom Route Matchers Como apresentado acima, a estrutura do Sinatra conta com suporte embutido para uso de padrões de String e expressões regulares como validadores de rota. No entanto, ele não pára por aí. Você pode facilmente definir os seus próprios validadores: ``` ruby class AllButPattern Match = Struct.new(:captures) def initialize(except) @except = except @captures = Match.new([]) end def match(str) @captures unless @except === str end end def all_but(pattern) AllButPattern.new(pattern) end get all_but("/index") do # ... end ``` Note que o exemplo acima pode ser robusto e complicado em excesso. Pode também ser implementado como: ``` ruby get // do pass if request.path_info == "/index" # ... end ``` Ou, usando algo mais denso à frente: ``` ruby get %r{^(?!/index$)} do # ... end ``` ## Arquivos estáticos Arquivos estáticos são disponibilizados a partir do diretório `./public`. Você pode especificar um local diferente pela opção `:public_folder` ``` ruby set :public_folder, File.dirname(__FILE__) + '/estatico' ``` Note que o nome do diretório público não é incluido na URL. Um arquivo `./public/css/style.css` é disponibilizado como `http://example.com/css/style.css`. ## Views / Templates Templates presumem-se estar localizados sob o diretório `./views`. Para utilizar um diretório view diferente: ``` ruby set :views, File.dirname(__FILE__) + '/modelo' ``` Uma coisa importante a ser lembrada é que você sempre tem as referências dos templates como símbolos, mesmo se eles estiverem em um sub-diretório (nesse caso utilize `:'subdir/template'`). Métodos de renderização irão processar qualquer string passada diretamente para elas. ### Haml Templates A gem/biblioteca haml é necessária para renderizar templates HAML: ``` ruby # Você precisa do 'require haml' em sua aplicação. require 'haml' get '/' do haml :index end ``` Renderiza `./views/index.haml`. [Opções Haml](http://haml.info/docs/yardoc/file.HAML_REFERENCE.html#options) podem ser setadas globalmente através das configurações do sinatra, veja [Opções e Configurações](http://www.sinatrarb.com/configuration.html), e substitua em uma requisição individual. ``` ruby set :haml, {:format => :html5 } # o formato padrão do Haml é :xhtml get '/' do haml :index, :haml_options => {:format => :html4 } # substituido end ``` ### Erb Templates ``` ruby # Você precisa do 'require erb' em sua aplicação require 'erb' get '/' do erb :index end ``` Renderiza `./views/index.erb` ### Erubis A gem/biblioteca erubis é necessária para renderizar templates erubis: ``` ruby # Você precisa do 'require erubis' em sua aplicação. require 'erubis' get '/' do erubis :index end ``` Renderiza `./views/index.erubis` ### Builder Templates A gem/biblioteca builder é necessária para renderizar templates builder: ``` ruby # Você precisa do 'require builder' em sua aplicação. require 'builder' get '/' do content_type 'application/xml', :charset => 'utf-8' builder :index end ``` Renderiza `./views/index.builder`. ### Sass Templates A gem/biblioteca sass é necessária para renderizar templates sass: ``` ruby # Você precisa do 'require haml' ou 'require sass' em sua aplicação. require 'sass' get '/stylesheet.css' do content_type 'text/css', :charset => 'utf-8' sass :stylesheet end ``` Renderiza `./views/stylesheet.sass`. [Opções Sass](http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#options) podem ser setadas globalmente através das configurações do sinatra, veja [Opções e Configurações](http://www.sinatrarb.com/configuration.html), e substitua em uma requisição individual. ``` ruby set :sass, {:style => :compact } # o estilo padrão do Sass é :nested get '/stylesheet.css' do content_type 'text/css', :charset => 'utf-8' sass :stylesheet, :style => :expanded # substituido end ``` ### Less Templates A gem/biblioteca less é necessária para renderizar templates Less: ``` ruby # Você precisa do 'require less' em sua aplicação. require 'less' get '/stylesheet.css' do content_type 'text/css', :charset => 'utf-8' less :stylesheet end ``` Renderiza `./views/stylesheet.less`. ### Inline Templates ``` ruby get '/' do haml '%div.title Olá Mundo' end ``` Renderiza a string, em uma linha, no template. ### Acessando Variáveis nos Templates Templates são avaliados dentro do mesmo contexto como manipuladores de rota. Variáveis de instância setadas em rotas manipuladas são diretamente acessadas por templates: ``` ruby get '/:id' do @foo = Foo.find(params[:id]) haml '%h1= @foo.nome' end ``` Ou, especifique um hash explícito para variáveis locais: ``` ruby get '/:id' do foo = Foo.find(params[:id]) haml '%h1= foo.nome', :locals => { :foo => foo } end ``` Isso é tipicamente utilizando quando renderizamos templates como partials dentro de outros templates. ### Templates Inline Templates podem ser definidos no final do arquivo fonte(.rb): ``` ruby require 'rubygems' require 'sinatra' get '/' do haml :index end __END__ @@ layout %html = yield @@ index %div.title Olá Mundo!!!!! ``` NOTA: Templates inline definidos no arquivo fonte são automaticamente carregados pelo sinatra. Digite \`enable :inline\_templates\` se você tem templates inline no outro arquivo fonte. ### Templates nomeados Templates também podem ser definidos utilizando o método top-level `template`: ``` ruby template :layout do "%html\n =yield\n" end template :index do '%div.title Olá Mundo!' end get '/' do haml :index end ``` Se existir um template com nome “layout”, ele será utilizado toda vez que um template for renderizado. Você pode desabilitar layouts passando `:layout => false`. ``` ruby get '/' do haml :index, :layout => !request.xhr? end ``` ## Helpers Use o método de alto nível `helpers` para definir métodos auxiliares para utilizar em manipuladores de rotas e modelos: ``` ruby helpers do def bar(nome) "#{nome}bar" end end get '/:nome' do bar(params[:nome]) end ``` ## Filtros Filtros Before são avaliados antes de cada requisição dentro do contexto da requisição e pode modificar a requisição e a reposta. Variáveis de instância setadas nos filtros são acessadas através de rotas e templates: ``` ruby before do @nota = 'Oi!' request.path_info = '/foo/bar/baz' end get '/foo/*' do @nota #=> 'Oi!' params[:splat] #=> 'bar/baz' end ``` Filtros After são avaliados após cada requisição dentro do contexto da requisição e também podem modificar o pedido e a resposta. Variáveis de instância definidas nos filtros before e rotas são acessadas através dos filtros after: ``` ruby after do puts response.status end ``` Filtros opcionalmente tem um padrão, fazendo com que sejam avaliados somente se o caminho do pedido coincidir com esse padrão: ``` ruby before '/protected/*' do authenticate! end after '/create/:slug' do |slug| session[:last_slug] = slug end ``` ## Halting Para parar imediatamente uma requisição com um filtro ou rota utilize: ``` ruby halt ``` Você também pode especificar o status quando parar… ``` ruby halt 410 ``` Ou com corpo de texto… ``` ruby halt 'isso será o corpo do texto' ``` Ou também… ``` ruby halt 401, 'vamos embora!' ``` Com cabeçalhos… ``` ruby halt 402, {'Content-Type' => 'text/plain'}, 'revanche' ``` ## Passing Uma rota pode processar aposta para a próxima rota correspondente usando `pass`: ``` ruby get '/adivinhar/:quem' do pass unless params[:quem] == 'Frank' 'Você me pegou!' end get '/adivinhar/*' do 'Você falhou!' end ``` O bloqueio da rota é imediatamente encerrado e o controle continua com a próxima rota de parâmetro. Se o parâmetro da rota não for encontrado, um 404 é retornado. ## Configuração Rodando uma vez, na inicialização, em qualquer ambiente: ``` ruby configure do ... end ``` Rodando somente quando o ambiente (`RACK_ENV` environment variável) é setado para `:production`: ``` ruby configure :production do ... end ``` Rodando quando o ambiente é setado para `:production` ou `:test`: ``` ruby configure :production, :test do ... end ``` ## Tratamento de Erros Tratamento de erros rodam dentro do mesmo contexto como rotas e filtros before, o que significa que você pega todos os presentes que tem para oferecer, como `haml`, `erb`, `halt`, etc. ### Não Encontrado Quando um `Sinatra::NotFound` exception é levantado, ou o código de status da reposta é 404, o `not_found` manipulador é invocado: ``` ruby not_found do 'Isto está longe de ser encontrado' end ``` ### Erro O manipulador `error` é invocado toda a vez que uma exceção é lançada a partir de um bloco de rota ou um filtro. O objeto da exceção pode ser obtido a partir da variável Rack `sinatra.error`: ``` ruby error do 'Desculpe, houve um erro desagradável - ' + env['sinatra.error'].name end ``` Erros customizados: ``` ruby error MeuErroCustomizado do 'Então que aconteceu foi...' + env['sinatra.error'].message end ``` Então, se isso acontecer: ``` ruby get '/' do raise MeuErroCustomizado, 'alguma coisa ruim' end ``` Você receberá isso: Então que aconteceu foi... alguma coisa ruim Alternativamente, você pode instalar manipulador de erro para um código de status: ``` ruby error 403 do 'Accesso negado' end get '/secreto' do 403 end ``` Ou um range: ``` ruby error 400..510 do 'Boom' end ``` O Sinatra instala os manipuladores especiais `not_found` e `error` quando roda sobre o ambiente de desenvolvimento. ## Mime Types Quando utilizamos `send_file` ou arquivos estáticos você pode ter mime types Sinatra não entendidos. Use `mime_type` para registrar eles por extensão de arquivos: ``` ruby mime_type :foo, 'text/foo' ``` Você também pode utilizar isto com o helper `content_type`: ``` ruby content_type :foo ``` ## Middleware Rack O Sinatra roda no [Rack](http://rack.rubyforge.org/), uma interface padrão mínima para frameworks web em Ruby. Um das capacidades mais interessantes do Rack para desenvolver aplicativos é suporte a “middleware” – componentes que ficam entre o servidor e sua aplicação monitorando e/ou manipulando o request/response do HTTP para prover vários tipos de funcionalidades comuns. O Sinatra faz construtores pipelines do middleware Rack facilmente em um nível superior utilizando o método `use`: ``` ruby require 'sinatra' require 'meu_middleware_customizado' use Rack::Lint use MeuMiddlewareCustomizado get '/ola' do 'Olá mundo' end ``` A semântica de `use` é idêntica aquela definida para a DSL [Rack::Builder](http://rack.rubyforge.org/doc/classes/Rack/Builder.html) (mais frequentemente utilizada para arquivos rackup). Por exemplo, o método `use` aceita múltiplos argumentos/variáveis bem como blocos: ``` ruby use Rack::Auth::Basic do |usuario, senha| usuario == 'admin' && senha == 'secreto' end ``` O Rack é distribuido com uma variedade de middleware padrões para logs, debugs, rotas de URL, autenticação, e manipuladores de sessão. Sinatra utilizada muitos desses componentes automaticamente baseando sobre configuração, então, tipicamente você não tem `use` explicitamente. ## Testando Testes no Sinatra podem ser escritos utilizando qualquer biblioteca ou framework de teste baseados no Rack. [Rack::Test](http://gitrdoc.com/brynary/rack-test) é recomendado: ``` ruby require 'minha_aplicacao_sinatra' require 'rack/test' class MinhaAplicacaoTeste < Test::Unit::TestCase include Rack::Test::Methods def app Sinatra::Application end def meu_test_default get '/' assert_equal 'Ola Mundo!', last_response.body end def teste_com_parametros get '/atender', :name => 'Frank' assert_equal 'Olá Frank!', last_response.bodymeet end def test_com_ambiente_rack get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' assert_equal "Você está utilizando o Songbird!", last_response.body end end ``` NOTA: Os módulos de classe embutidos `Sinatra::Test` e `Sinatra::TestHarness` são depreciados na versão 0.9.2. ## Sinatra::Base - Middleware, Bibliotecas e aplicativos modulares Definir sua aplicação em um nível superior de trabalho funciona bem para micro aplicativos, mas tem consideráveis incovenientes na construção de componentes reutilizáveis como um middleware Rack, metal Rails, bibliotecas simples como um componente de servidor, ou mesmo extensões Sinatra. A DSL de nível superior polui o espaço do objeto e assume um estilo de configuração de micro aplicativos (exemplo: uma simples arquivo de aplicação, diretórios `./public` e `./views`, logs, página de detalhes de exceção, etc.). É onde o `Sinatra::Base` entra em jogo: ``` ruby require 'sinatra/base' class MinhaApp < Sinatra::Base set :sessions, true set :foo, 'bar' get '/' do 'Ola mundo!' end end ``` A classe `MinhaApp` é um componente Rack independente que pode agir como um middleware Rack, uma aplicação Rack, ou metal Rails. Você pode utilizar ou executar esta classe com um arquivo rackup `config.ru`; ou, controlar um componente de servidor fornecendo como biblioteca: ``` ruby MinhaApp.run! :host => 'localhost', :port => 9090 ``` Os métodos disponíveis para subclasses `Sinatra::Base` são exatamente como aqueles disponíveis via a DSL de nível superior. Aplicações de nível mais alto podem ser convertidas para componentes `Sinatra::Base` com duas modificações: - Seu arquivo deve requerer `sinatra/base` ao invés de `sinatra`; outra coisa, todos os métodos DSL do Sinatra são importados para o espaço principal. - Coloque as rotas da sua aplicação, manipuladores de erro, filtros e opções na subclasse de um `Sinatra::Base`. `Sinatra::Base` é um quadro branco. Muitas opções são desabilitadas por padrão, incluindo o servidor embutido. Veja [Opções e Configurações](http://sinatra.github.com/configuration.html) para detalhes de opções disponíveis e seus comportamentos. SIDEBAR: A DSL de alto nível do Sinatra é implementada utilizando um simples sistema de delegação. A classe `Sinatra::Application` – uma subclasse especial da `Sinatra::Base` – recebe todos os `:get`, `:put`, `:post`, `:delete`, `:before`, `:error`, `:not_found`, `:configure`, e `:set messages` enviados para o alto nível. Dê uma olhada no código você mesmo: aqui está o [Sinatra::Delegator mixin](http://github.com/sinatra/sinatra/blob/ceac46f0bc129a6e994a06100aa854f606fe5992/lib/sinatra/base.rb#L1128) sendo [incluido dentro de um espaço principal](http://github.com/sinatra/sinatra/blob/ceac46f0bc129a6e994a06100aa854f606fe5992/lib/sinatra/main.rb#L28) ## Linha de Comando Aplicações Sinatra podem ser executadas diretamente: ``` shell ruby minhaapp.rb [-h] [-x] [-e AMBIENTE] [-p PORTA] [-o HOST] [-s SERVIDOR] ``` As opções são: ``` -h # ajuda -p # define a porta (padrão é 4567) -o # define o host (padrão é 0.0.0.0) -e # define o ambiente (padrão é development) -s # especifica o servidor/manipulador rack (padrão é thin) -x # ativa o bloqueio (padrão é desligado) ``` ## A última versão Se você gostaria de utilizar o código da última versão do Sinatra, crie um clone local e execute sua aplicação com o diretório `sinatra/lib` no `LOAD_PATH`: ``` shell cd minhaapp git clone git://github.com/sinatra/sinatra.git ruby -I sinatra/lib minhaapp.rb ``` Alternativamente, você pode adicionar o diretório do `sinatra/lib` no `LOAD_PATH` do seu aplicativo: ``` ruby $LOAD_PATH.unshift File.dirname(__FILE__) + '/sinatra/lib' require 'rubygems' require 'sinatra' get '/sobre' do "Estou rodando a versão" + Sinatra::VERSION end ``` Para atualizar o código do Sinatra no futuro: ``` shell cd meuprojeto/sinatra git pull ``` ## Mais - [Website do Projeto](http://www.sinatrarb.com/) - Documentação adicional, novidades e links para outros recursos. - [Contribuir](http://www.sinatrarb.com/contributing) - Encontrar um bug? Precisa de ajuda? Tem um patch? - [Acompanhar Questões](http://github.com/sinatra/sinatra/issues) - [Twitter](http://twitter.com/sinatra) - [Lista de Email](http://groups.google.com/group/sinatrarb/topics) - [IRC: \#sinatra](irc://chat.freenode.net/#sinatra) em [freenode.net](http://freenode.net) sinatra-1.4.3/CHANGES0000644000004100000410000014114312161612727014214 0ustar www-datawww-data= 1.4.3 / 2013-06-07 * Running a Sinatra file directly or via `run!` it will now ignore an empty $PORT env variable. (noxqsgit) * Improve documentation. (burningTyger, Patricio Mac Adden, Konstantin Haase, Diogo Scudelletti, Dominic Imhof) * Expose matched pattern as env["sinatra.route"]. (Aman Gupta) * Fix warning on Ruby 2.0. (Craig Little) * Improve running subset of tests in isolation. (Viliam Pucik) * Reorder private/public methods. (Patricio Mac Adden) * Loosen version dependency for rack, so it runs with Rails 3.2. (Konstantin Haase) * Request#accept? now returns true instead of a truthy value. (Alan Harris) = 1.4.2 / 2013-03-21 * Fix parsing error for case where both the pattern and the captured part contain a dot. (Florian Hanke, Konstantin Haase) * Missing Accept header is treated like */*. (Greg Denton) * Improve documentation. (Patricio Mac Adden, Joe Bottigliero) = 1.4.1 / 2013-03-15 * Make delegated methods available in config.ru (Konstantin Haase) = 1.4.0 / 2013-03-15 * Add support for LINK and UNLINK requests. (Konstantin Haase) * Add support for Yajl templates. (Jamie Hodge) * Add support for Rabl templates. (Jesse Cooke) * Add support for Wlang templates. (Bernard Lambeau) * Add support for Stylus templates. (Juan David Pastas, Konstantin Haase) * You can now pass a block to ERb, Haml, Slim, Liquid and Wlang templates, which will be used when calling `yield` in the template. (Alexey Muranov) * When running in classic mode, no longer include Sinatra::Delegator in Object, instead extend the main object only. (Konstantin Haase) * Improved route parsing: "/:name.?:format?" with "/foo.png" now matches to {name: "foo", format: "png"} instead of {name: "foo.png"}. (Florian Hanke) * Add :status option support to send_file. (Konstantin Haase) * The `provides` condition now respects an earlier set content type. (Konstantin Haase) * Exception#code is only used when :use_code is enabled. Moreover, it will be ignored if the value is not between 400 and 599. You should use Exception#http_status instead. (Konstantin Haase) * Status, headers and body will be set correctly in an after filter when using halt in a before filter or route. (Konstantin Haase) * Sinatra::Base.new now returns a Sinatra::Wrapper instance, exposing #settings and #helpers, yet going through the middleware stack on #call. It also implements a nice #inspect, so it plays nice with Rails' `rake routes`. (Konstantin Haase) * In addition to WebRick, Thin and Mongrel, Sinatra will now automatically pick up Puma, Trinidad, ControlTower or Net::HTTP::Server when installed. The logic for picking the server has been improved and now depends on the Ruby implementation used. (Mark Rada, Konstantin Haase, Patricio Mac Adden) * "Sinatra doesn't know this ditty" pages now show the app class when running a modular application. This helps detecting where the response came from when combining multiple modular apps. (Konstantin Haase) * When port is not set explicitly, use $PORT env variable if set and only default to 4567 if not. Plays nice with foreman. (Konstantin Haase) * Allow setting layout on a per engine basis. (Zachary Scott, Konstantin Haase) * You can now use `register` directly in a classic app. (Konstantin Haase) * `redirect` now accepts URI or Addressable::URI instances. (Nicolas Sanguinetti) * Have Content-Disposition header also include file name for `inline`, not just for `attachment`. (Konstantin Haase) * Better compatibility to Rack 1.5. (James Tucker, Konstantin Haase) * Make route parsing regex more robust. (Zoltan Dezso, Konstantin Haase) * Improve Accept header parsing, expose parameters. (Pieter van de Bruggen, Konstantin Haase) * Add `layout_options` render option. Allows you, amongst other things, to render a layout from a different folder. (Konstantin Haase) * Explicitly setting `layout` to `nil` is treated like setting it to `false`. (richo) * Properly escape attributes in Content-Type header. (Pieter van de Bruggen) * Default to only serving localhost in development mode. (Postmodern) * Setting status code to 404 in error handler no longer triggers not_found handler. (Konstantin Haase) * The `protection` option now takes a `session` key for force disabling/enabling session based protections. (Konstantin Haase) * Add `x_cascade` option to disable `X-Cascade` header on missing route. (Konstantin Haase) * Improve documentation. (Kashyap, Stanislav Chistenko, Zachary Scott, Anthony Accomazzo, Peter Suschlik, Rachel Mehl, ymmtmsys, Anurag Priyam, burningTyger, Tony Miller, akicho8, Vasily Polovnyov, Markus Prinz, Alexey Muranov, Erik Johnson, Vipul A M, Konstantin Haase) * Convert documentation to Markdown. (Kashyap, Robin Dupret, burningTyger, Vasily Polovnyov, Iain Barnett, Giuseppe Capizzi, Neil West) * Don't set not_found content type to HTML in development mode with custom not_found handler. (Konstantin Haase) * Fix mixed indentation for private methods. (Robin Dupret) * Recalculate Content-Length even if hard coded if body is reset. Relevant mostly for error handlers. (Nathan Esquenazi, Konstantin Haase) * Plus sign is once again kept as such when used for URL matches. (Konstantin Haase) * Take views option into account for template caching. (Konstantin Haase) * Consistent use of `headers` instead of `header` internally. (Patricio Mac Adden) * Fix compatibility to RDoc 4. (Bohuslav Kabrda) * Make chat example work with latest jQuery. (loveky, Tony Miller) * Make tests run without warnings. (Patricio Mac Adden) * Make sure value returned by `mime_type` is a String or nil, even when a different object is passed in, like an AcceptEntry. (Konstantin Haase) * Exceptions in `after` filter are now handled like any other exception. (Nathan Esquenazi) = 1.3.6 (backport release) / 2013-03-15 Backported from 1.4.0: * Take views option into account for template caching. (Konstantin Haase) * Improve documentation (Konstantin Haase) * No longer override `define_singleton_method`. (Konstantin Haase) = 1.3.5 / 2013-02-25 * Fix for RubyGems 2.0 (Uchio KONDO) * Improve documentation (Konstantin Haase) * No longer override `define_singleton_method`. (Konstantin Haase) = 1.3.4 / 2013-01-26 * Improve documentation. (Kashyap, Stanislav Chistenko, Konstantin Haase, ymmtmsys, Anurag Priyam) * Adjustments to template system to work with Tilt edge. (Konstantin Haase) * Fix streaming with latest Rack release. (Konstantin Haase) * Fix default content type for Sinatra::Response with latest Rack release. (Konstantin Haase) * Fix regression where + was no longer treated like space. (Ross Boucher) * Status, headers and body will be set correctly in an after filter when using halt in a before filter or route. (Konstantin Haase) = 1.3.3 / 2012-08-19 * Improved documentation. (burningTyger, Konstantin Haase, Gabriel Andretta, Anurag Priyam, michelc) * No longer modify the load path. (Konstantin Haase) * When keeping a stream open, set up callback/errback correctly to deal with clients closing the connection. (Konstantin Haase) * Fix bug where having a query param and a URL param by the same name would concatenate the two values. (Konstantin Haase) * Prevent duplicated log output when application is already wrapped in a `Rack::CommonLogger`. (Konstantin Haase) * Fix issue where `Rack::Link` and Rails were preventing indefinite streaming. (Konstantin Haase) * No longer cause warnings when running Ruby with `-w`. (Konstantin Haase) * HEAD requests on static files no longer report a Content-Length of 0, but instead the proper length. (Konstantin Haase) * When protecting against CSRF attacks, drop the session instead of refusing the request. (Konstantin Haase) = 1.3.2 / 2011-12-30 * Don't automatically add `Rack::CommonLogger` if `Rack::Server` is adding it, too. (Konstantin Haase) * Setting `logging` to `nil` will avoid setting up `Rack::NullLogger`. (Konstantin Haase) * Route specific params are now available in the block passed to #stream. (Konstantin Haase) * Fix bug where rendering a second template in the same request, after the first one raised an exception, skipped the default layout. (Nathan Baum) * Fix bug where parameter escaping got enabled when disabling a different protection. (Konstantin Haase) * Fix regression: Filters without a pattern may now again manipulate the params hash. (Konstantin Haase) * Added examples directory. (Konstantin Haase) * Improved documentation. (Gabriel Andretta, Markus Prinz, Erick Zetta, Just Lest, Adam Vaughan, Aleksander Dąbrowski) * Improved MagLev support. (Tim Felgentreff) = 1.3.1 / 2011-10-05 * Support adding more than one callback to the stream object. (Konstantin Haase) * Fix for infinite loop when streaming on 1.9.2 with Thin from a modular application (Konstantin Haase) = 1.3.0 / 2011-09-30 * 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. (Josh Lane, Jacob Burkhart, Konstantin Haase) * Support for Creole templates, Creole is a standardized wiki markup, supported by many wiki implementations. (Konstanin Haase) * The `erubis` method has been deprecated. If Erubis is available, Sinatra will automatically use it for rendering ERB templates. `require 'erb'` explicitly to prevent that behavior. (Magnus Holm, Ryan Tomayko, Konstantin Haase) * Patterns now match against the escaped URLs rather than the unescaped 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. (Konstantin Haase) * `settings.app_file` now defaults to the file subclassing `Sinatra::Base` in modular applications. (Konstantin Haase) * Set up `Rack::Logger` or `Rack::NullLogger` depending on whether logging was enabled or not. Also, expose that logger with the `logger` helper method. (Konstantin Haase) * The sessions setting may be an options hash now. (Konstantin Haase) * Important: Ruby 1.8.6 support has been dropped. This version also depends on at least Rack 1.3.0. This means that it is incompatible with Rails prior to 3.1.0. Please use 1.2.x if you require an earlier version of Ruby or Rack, which we will continue to supply with bug fixes. (Konstantin Haase) * Renamed `:public` to `:public_folder` to avoid overriding Ruby's built-in `public` method/keyword. `set(:public, ...)` is still possible but shows a warning. (Konstantin Haase) * It is now possible to use a different target class for the top level DSL (aka classic style) than `Sinatra::Application` by setting `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 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) * Added `request.accept?` and `request.preferred_type` to ease dealing with `Accept` headers. (Konstantin Haase) * Added `:static_cache_control` setting to automatically set cache control headers to static files. (Kenichi Nakamura) * Added `informal?`, `success?`, `redirect?`, `client_error?`, `server_error?` and `not_found?` helper methods to ease dealing with status codes. (Konstantin Haase) * Uses SecureRandom to generate default session secret. (Konstantin Haase) * 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) * The `last_modified` helper does not stop execution and change the status code if the status code is something different than 200. (Konstantin Haase) * Added support for If-Unmodified-Since header. (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) * Also specify charset in Content-Type header for JSON. (Konstantin Haase) * Rack handler names will not be converted to lower case internally, this 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) * Internal refactoring and minor performance improvements. (Konstantin Haase) * 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.9 (backports release) / 2013-03-15 IMPORTANT: THIS IS THE LAST 1.2.x RELEASE, PLEASE UPGRADE. * Display EOL warning when loading Sinatra. (Konstantin Haase) * Improve documentation. (Anurag Priyam, Konstantin Haase) * Do not modify the load path. (Konstantin Haase) * Display deprecation warning if RUBY_IGNORE_CALLERS is used. (Konstantin Haase) * Add backports library so we can still run on Ruby 1.8.6. (Konstantin Haase) = 1.2.8 (backports release) / 2011-12-30 Backported from 1.3.2: * Fix bug where rendering a second template in the same request after the first one raised an exception skipped the default layout (Nathan Baum) = 1.2.7 (backports release) / 2011-09-30 Custom changes: * Fix Ruby 1.8.6 issue with Accept header parsing. (Konstantin Haase) 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 (Konstantin Haase) * Improved documentation. (Emanuele Vicentini, Peter Higgins, Takanori Ishikawa, Konstantin Haase) * Also specify charset in Content-Type header for JSON. (Konstantin Haase) * Rack handler names will not be converted to lower case internally, this allows you to run Sinatra with custom Rack handlers, like Kirk or Mongrel2. Example: `ruby app.rb -s Mongrel2` (Konstantin Haase) * 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. (Konstantin Haase) = 1.2.5 / 2011-04-30 * Restore compatibility with Ruby 1.8.6. (Konstantin Haase) = 1.2.4 / 2011-04-30 * Sinatra::Application (classic style) does not use a session secret in development mode, so sessions are not invalidated after every request when using Shotgun. (Konstantin Haase) * The request object was shared between multiple Sinatra instances in the same middleware chain. This caused issues if any non-sinatra routing happend in-between two of those instances, or running a request twice against an application (described in the README). The caching was reverted. See GH#239 and GH#256 for more infos. (Konstantin Haase) * Fixes issues where the top level DSL was interfering with method_missing proxies. This issue surfaced when Rails 3 was used with older Sass versions and Sinatra >= 1.2.0. (Konstantin Haase) * Sinatra::Delegator.delegate is now able to delegate any method names, even those containing special characters. This allows better integration into other programming languages on Rubinius (probably on the JVM, too), like Fancy. (Konstantin Haase) * Remove HEAD request logic and let Rack::Head handle it instead. (Paolo "Nusco" Perrotta) = 1.2.3 / 2011-04-13 * This release is compatible with Tilt 1.3, it will still work with Tilt 1.2.2, however, if you want to use a newer Tilt version, you have to upgrade to at least this version of Sinatra. (Konstantin Haase) * Helpers dealing with time, like `expires`, handle objects that pretend to be numbers, like `ActiveSupport::Duration`, better. (Konstantin Haase) = 1.2.2 / 2011-04-08 * The `:provides => :js` condition now matches both `application/javascript` and `text/javascript`. The `:provides => :xml` condition now matches both `application/xml` and `text/xml`. The `Content-Type` header is set accordingly. If the client accepts both, the `application/*` version is preferred, since the `text/*` versions are deprecated. (Konstantin Haase) * The `provides` condition now handles wildcards in `Accept` headers correctly. Thus `:provides => :html` matches `text/html`, `text/*` and `*/*`. (Konstantin Haase) * When parsing `Accept` headers, `Content-Type` preferences are honored according to RFC 2616 section 14.1. (Konstantin Haase) * URIs passed to the `url` helper or `redirect` may now use any schema to be identified as absolute URIs, not only `http` or `https`. (Konstantin Haase) * Handles `Content-Type` strings that already contain parameters correctly in `content_type` (example: `content_type "text/plain; charset=utf-16"`). (Konstantin Haase) * If a route with an empty pattern is defined (`get("") { ... }`) requests with an empty path info match this route instead of "/". (Konstantin Haase) * In development environment, when running under a nested path, the image URIs on the error pages are set properly. (Konstantin Haase) = 1.2.1 / 2011-03-17 * Use a generated session secret when using `enable :sessions`. (Konstantin Haase) * Fixed a bug where the wrong content type was used if no content type was set and a template engine was used with a different engine for the layout with different default content types, say Less embedded in Slim. (Konstantin Haase) * README translations improved (Gabriel Andretta, burningTyger, Sylvain Desvé, Gregor Schmidt) = 1.2.0 / 2011-03-03 * Added `slim` rendering method for rendering Slim templates. (Steve Hodgkiss) * The `markaby` rendering method now allows passing a block, making inline usage possible. Requires Tilt 1.2 or newer. (Konstantin Haase) * All render methods now take a `:layout_engine` option, allowing to use a layout in a different template language. Even more useful than using this directly (`erb :index, :layout_engine => :haml`) is setting this globally for a template engine that otherwise does not support layouts, like Markdown or Textile (`set :markdown, :layout_engine => :erb`). (Konstantin Haase) * Before and after filters now support conditions, both with and without patterns (`before '/api/*', :agent => /Songbird/`). (Konstantin Haase) * Added a `url` helper method which constructs absolute URLs. Copes with reverse proxies and Rack handlers correctly. Aliased to `to`, so you can write `redirect to('/foo')`. (Konstantin Haase) * If running on 1.9, patterns for routes and filters now support named captures: `get(%r{/hi/(?[^/?#]+)}) { "Hi #{params['name']}" }`. (Steve Price) * All rendering methods now take a `:scope` option, which renders them in another context. Note that helpers and instance variables will be unavailable if you use this feature. (Paul Walker) * The behavior of `redirect` can now be configured with `absolute_redirects` and `prefixed_redirects`. (Konstantin Haase) * `send_file` now allows overriding the Last-Modified header, which defaults to the file's mtime, by passing a `:last_modified` option. (Konstantin Haase) * You can use your own template lookup method by defining `find_template`. This allows, among other things, using more than one views folder. (Konstantin Haase) * Largely improved documentation. (burningTyger, Vasily Polovnyov, Gabriel Andretta, Konstantin Haase) * Improved error handling. (cactus, Konstantin Haase) * Skip missing template engines in tests correctly. (cactus) * Sinatra now ships with a Gemfile for development dependencies, since it eases supporting different platforms, like JRuby. (Konstantin Haase) = 1.1.4 (backports release) / 2011-04-13 * Compatible with Tilt 1.3. (Konstantin Haase) = 1.1.3 / 2011-02-20 * Fixed issues with `user_agent` condition if the user agent header is missing. (Konstantin Haase) * Fix some routing tests that have been skipped by accident (Ross A. Baker) * Fix rendering issues with Builder and Nokogiri (Konstantin Haase) * Replace last_modified helper with better implementation. (cactus, Konstantin Haase) * Fix issue with charset not being set when using `provides` condition. (Konstantin Haase) * Fix issue with `render` not picking up all alternative file extensions for a rendering engine - it was not possible to register ".html.erb" without tricks. (Konstantin Haase) = 1.1.2 / 2010-10-25 Like 1.1.1, but with proper CHANGES file. = 1.1.1 / 2010-10-25 * README has been translated to Russian (Nickolay Schwarz, Vasily Polovnyov) and Portuguese (Luciano Sousa). * Nested templates without a `:layout` option can now be used from the layout template without causing an infinite loop. (Konstantin Haase) * Inline templates are now encoding aware and can therefore be used with unicode characters on Ruby 1.9. Magic comments at the beginning of the file will be honored. (Konstantin Haase) * Default `app_file` is set correctly when running with bundler. Using bundler caused Sinatra not to find the `app_file` and therefore not to find the `views` folder on it's own. (Konstantin Haase) * Better handling of Content-Type when using `send_file`: If file extension is unknown, fall back to `application/octet-stream` and do not override content type if it has already been set, except if `:type` is passed explicitly (Konstantin Haase) * Path is no longer cached if changed between handlers that do pattern matching. This means you can change `request.path_info` in a pattern matching before filter. (Konstantin Haase) * Headers set by cache_control now always set max_age as an Integer, making sure it is compatible with RFC2616. (Konstantin Haase) * Further improved handling of string encodings on Ruby 1.9, templates now honor default_encoding and URLs support unicode characters. (Konstantin Haase) = 1.1.0 / 2010-10-24 * Before and after filters now support pattern matching, including the ability to use captures: "before('/user/:name') { |name| ... }". This avoids manual path checking. No performance loss if patterns are avoided. (Konstantin Haase) * It is now possible to render SCSS files with the `scss` method, which behaves exactly like `sass` except for the different file extension and assuming the SCSS syntax. (Pedro Menezes, Konstantin Haase) * Added `liquid`, `markdown`, `nokogiri`, `textile`, `rdoc`, `radius`, `markaby`, and `coffee` rendering methods for rendering Liquid, Markdown, Nokogiri, Textile, RDoc, Radius, Markaby and CoffeeScript templates. (Konstantin Haase) * Now supports byte-range requests (the HTTP_RANGE header) for static files. Multi-range requests are not supported, however. (Jens Alfke) * You can now use #settings method from class and top level for convenience. (Konstantin Haase) * Setting multiple values now no longer relies on #to_hash and therefore accepts any Enumerable as parameter. (Simon Rozet) * Nested templates default the `layout` option to `false` rather than `true`. This eases the use of partials. If you wanted to render one haml template embedded in another, you had to call `haml :partial, {}, :layout => false`. As you almost never want the partial to be wrapped in the standard layout in this situation, you now only have to call `haml :partial`. Passing in `layout` explicitly is still possible. (Konstantin Haase) * If a the return value of one of the render functions is used as a response body and the content type has not been set explicitly, Sinatra chooses a content type corresponding to the rendering engine rather than just using "text/html". (Konstantin Haase) * README is now available in Chinese (Wu Jiang), French (Mickael Riga), German (Bernhard Essl, Konstantin Haase, burningTyger), Hungarian (Janos Hardi) and Spanish (Gabriel Andretta). The extremely outdated Japanese README has been updated (Kouhei Yanagita). * It is now possible to access Sinatra's template_cache from the outside. (Nick Sutterer) * The `last_modified` method now also accepts DateTime instances and makes sure the header will always be set to a string. (Konstantin Haase) * 599 now is a legal status code. (Steve Shreeve) * This release is compatible with Ruby 1.9.2. Sinatra was trying to read non existent files Ruby added to the call stack. (Shota Fukumori, Konstantin Haase) * Prevents a memory leak on 1.8.6 in production mode. Note, however, that this is due to a bug in 1.8.6 and request will have the additional overhead of parsing templates again on that version. It is recommended to use at least Ruby 1.8.7. (Konstantin Haase) * Compares last modified date correctly. `last_modified` was halting only when the 'If-Modified-Since' header date was equal to the time specified. Now, it halts when is equal or later than the time specified (Gabriel Andretta). * Sinatra is now usable in combination with Rails 3. When mounting a Sinatra application under a subpath in Rails 3, the PATH_INFO is not prefixed with a slash and no routes did match. (José Valim) * Better handling of encodings in 1.9, defaults params encoding to UTF-8. (Konstantin Haase) * `show_exeptions` handling is now triggered after custom error handlers, if it is set to `:after_handlers`, thus not disabling those handler in development mode. (pangel, Konstantin Haase) * Added ability to handle weighted HTTP_ACCEPT headers. (Davide D'Agostino) * `send_file` now always respects the `:type` option if set. Previously it was discarded if no matching mime type was found, which made it impossible to directly pass a mime type. (Konstantin Haase) * `redirect` always redirects to an absolute URI, even if a relative URI was passed. Ensures compatibility with RFC 2616 section 14.30. (Jean-Philippe Garcia Ballester, Anthony Williams) * Broken examples for using Erubis, Haml and Test::Unit in README have been fixed. (Nick Sutterer, Doug Ireton, Jason Stewart, Eric Marden) * Sinatra now handles SIGTERM correctly. (Patrick Collison) * Fixes an issue with inline templates in modular applications that manually call `run!`. (Konstantin Haase) * Spaces after inline template names are now ignored (Konstantin Haase) * It's now possible to use Sinatra with different package management systems defining a custom require. (Konstantin Haase) * Lighthouse has been dropped in favor of GitHub issues. * Tilt is now a dependency and therefore no longer ships bundled with Sinatra. (Ryan Tomayko, Konstantin Haase) * Sinatra now depends on Rack 1.1 or higher. Rack 1.0 is no longer supported. (Konstantin Haase) = 1.0 / 2010-03-23 * It's now possible to register blocks to run after each request using after filters. After filters run at the end of each request, after routes and error handlers. (Jimmy Schementi) * Sinatra now uses Tilt for rendering templates. This adds support for template caching, consistent template backtraces, and support for new template engines, like mustache and liquid. (Ryan Tomayko) * ERB, Erubis, and Haml templates are now compiled the first time they're rendered instead of being string eval'd on each invocation. Benchmarks show a 5x-10x improvement in render time. This also reduces the number of objects created, decreasing pressure on Ruby's GC. (Ryan Tomayko) * New 'settings' method gives access to options in both class and request scopes. This replaces the 'options' method. (Chris Wanstrath) * New boolean 'reload_templates' setting controls whether template files are reread from disk and recompiled on each request. Template read/compile is cached by default in all environments except development. (Ryan Tomayko) * New 'erubis' helper method for rendering ERB template with Erubis. The erubis gem is required. (Dylan Egan) * New 'cache_control' helper method provides a convenient way of setting the Cache-Control response header. Takes a variable number of boolean directives followed by a hash of value directives, like this: cache_control :public, :must_revalidate, :max_age => 60 (Ryan Tomayko) * New 'expires' helper method is like cache_control but takes an integer number of seconds or Time object: expires 300, :public, :must_revalidate (Ryan Tomayko) * New request.secure? method for checking for an SSL connection. (Adam Wiggins) * Sinatra apps can now be run with a `-o ` argument to specify the address to bind to. (Ryan Tomayko) * Rack::Session::Cookie is now added to the middleware pipeline when running in test environments if the :sessions option is set. (Simon Rozet) * Route handlers, before filters, templates, error mappings, and middleware are now resolved dynamically up the inheritance hierarchy when needed instead of duplicating the superclass's version when a new Sinatra::Base subclass is created. This should fix a variety of issues with extensions that need to add any of these things to the base class. (Ryan Tomayko) * Exception error handlers always override the raise_errors option now. Previously, all exceptions would be raised outside of the application when the raise_errors option was enabled, even if an error handler was defined for that exception. The raise_errors option now controls whether unhandled exceptions are raised (enabled) or if a generic 500 error is returned (disabled). (Ryan Tomayko) * The X-Cascade response header is set to 'pass' when no matching route is found or all routes pass. (Josh Peek) * Filters do not run when serving static files anymore. (Ryan Tomayko) * pass takes an optional block to be used as the route handler if no subsequent route matches the request. (Blake Mizerany) The following Sinatra features have been obsoleted (removed entirely) in the 1.0 release: * The `sinatra/test` library is obsolete. This includes the `Sinatra::Test` module, the `Sinatra::TestHarness` class, and the `get_it`, `post_it`, `put_it`, `delete_it`, and `head_it` helper methods. The [`Rack::Test` library](http://gitrdoc.com/brynary/rack-test) should be used instead. * Test framework specific libraries (`sinatra/test/spec`, `sinatra/test/bacon`,`sinatra/test/rspec`, etc.) are obsolete. See http://www.sinatrarb.com/testing.html for instructions on setting up a testing environment under each of these frameworks. * `Sinatra::Default` is obsolete; use `Sinatra::Base` instead. `Sinatra::Base` acts more like `Sinatra::Default` in development mode. For example, static file serving and sexy development error pages are enabled by default. * Auto-requiring template libraries in the `erb`, `builder`, `haml`, and `sass` methods is obsolete due to thread-safety issues. You must require the template libraries explicitly in your app. * The `:views_directory` option to rendering methods is obsolete; use `:views` instead. * The `:haml` and `:sass` options to rendering methods are obsolete. Template engine options should be passed in the second Hash argument instead. * The `use_in_file_templates` method is obsolete. Use `enable :inline_templates` or `set :inline_templates, 'path/to/file'` * The 'media_type' helper method is obsolete. Use 'mime_type' instead. * The 'mime' main and class method is obsolete. Use 'mime_type' instead. * The request-level `send_data` method is no longer supported. * The `Sinatra::Event` and `Sinatra::EventContext` classes are no longer supported. This may effect extensions written for versions prior to 0.9.2. See [Writing Sinatra Extensions](http://www.sinatrarb.com/extensions.html) for the officially supported extensions API. * The `set_option` and `set_options` methods are obsolete; use `set` instead. * The `:env` setting (`settings.env`) is obsolete; use `:environment` instead. * The request level `stop` method is obsolete; use `halt` instead. * The request level `entity_tag` method is obsolete; use `etag` instead. * The request level `headers` method (HTTP response headers) is obsolete; use `response['Header-Name']` instead. * `Sinatra.application` is obsolete; use `Sinatra::Application` instead. * Using `Sinatra.application = nil` to reset an application is obsolete. This should no longer be necessary. * Using `Sinatra.default_options` to set base configuration items is obsolete; use `Sinatra::Base.set(key, value)` instead. * The `Sinatra::ServerError` exception is obsolete. All exceptions raised within a request are now treated as internal server errors and result in a 500 response status. * The `:methodoverride' option to enable/disable the POST _method hack is obsolete; use `:method_override` instead. = 0.9.2 / 2009-05-18 * This version is compatible with Rack 1.0. [Rein Henrichs] * The development-mode unhandled exception / error page has been greatly enhanced, functionally and aesthetically. The error page is used when the :show_exceptions option is enabled and an exception propagates outside of a route handler or before filter. [Simon Rozet / Matte Noble / Ryan Tomayko] * Backtraces that move through templates now include filenames and line numbers where possible. [#51 / S. Brent Faulkner] * All templates now have an app-level option for setting default template options (:haml, :sass, :erb, :builder). The app-level option value must be a Hash if set and is merged with the template options specified to the render method (Base#haml, Base#erb, Base#builder). [S. Brent Faulkner, Ryan Tomayko] * The method signature for all template rendering methods has been unified: "def engine(template, options={}, locals={})". The options Hash now takes the generic :views, :layout, and :locals options but also any template-specific options. The generic options are removed before calling the template specific render method. Locals may be specified using either the :locals key in the options hash or a second Hash option to the rendering method. [#191 / Ryan Tomayko] * The receiver is now passed to "configure" blocks. This allows for the following idiom in top-level apps: configure { |app| set :foo, app.root + '/foo' } [TJ Holowaychuck / Ryan Tomayko] * The "sinatra/test" lib is deprecated and will be removed in Sinatra 1.0. This includes the Sinatra::Test module and Sinatra::TestHarness class in addition to all the framework test helpers that were deprecated in 0.9.1. The Rack::Test lib should be used instead: http://gitrdoc.com/brynary/rack-test [#176 / Simon Rozet] * Development mode source file reloading has been removed. The "shotgun" (http://rtomayko.github.com/shotgun/) program can be used to achieve the same basic functionality in most situations. Passenger users should use the "tmp/always_restart.txt" file (http://tinyurl.com/c67o4h). [#166 / Ryan Tomayko] * Auto-requiring template libs in the erb, builder, haml, and sass methods is deprecated due to thread-safety issues. You must require the template libs explicitly in your app file. [Simon Rozet] * A new Sinatra::Base#route_missing method was added. route_missing is sent when no route matches the request or all route handlers pass. The default implementation forwards the request to the downstream app when running as middleware (i.e., "@app" is non-nil), or raises a NotFound exception when no downstream app is defined. Subclasses can override this method to perform custom route miss logic. [Jon Crosby] * A new Sinatra::Base#route_eval method was added. The method yields to the block and throws :halt with the result. Subclasses can override this method to tap into the route execution logic. [TJ Holowaychuck] * Fix the "-x" (enable request mutex / locking) command line argument. Passing -x now properly sets the :lock option. [S. Brent Faulkner, Ryan Tomayko] * Fix writer ("foo=") and predicate ("foo?") methods in extension modules not being added to the registering class. [#172 / Pat Nakajima] * Fix in-file templates when running alongside activesupport and fatal errors when requiring activesupport before sinatra [#178 / Brian Candler] * Fix various issues running on Google AppEngine. [Samuel Goebert, Simon Rozet] * Fix in-file templates __END__ detection when __END__ exists with other stuff on a line [Yoji Shidara] = 0.9.1.1 / 2009-03-09 * Fix directory traversal vulnerability in default static files route. See [#177] for more info. = 0.9.1 / 2009-03-01 * Sinatra now runs under Ruby 1.9.1 [#61] * Route patterns (splats, :named, or Regexp captures) are now passed as arguments to the block. [#140] * The "helpers" method now takes a variable number of modules along with the normal block syntax. [#133] * New request-level #forward method for middleware components: passes the env to the downstream app and merges the response status, headers, and body into the current context. [#126] * Requests are now automatically forwarded to the downstream app when running as middleware and no matching route is found or all routes pass. * New simple API for extensions/plugins to add DSL-level and request-level methods. Use Sinatra.register(mixin) to extend the DSL with all public methods defined in the mixin module; use Sinatra.helpers(mixin) to make all public methods defined in the mixin module available at the request level. [#138] See http://www.sinatrarb.com/extensions.html for details. * Named parameters in routes now capture the "." character. This makes routes like "/:path/:filename" match against requests like "/foo/bar.txt"; in this case, "params[:filename]" is "bar.txt". Previously, the route would not match at all. * Added request-level "redirect back" to redirect to the referring URL. * Added a new "clean_trace" option that causes backtraces dumped to rack.errors and displayed on the development error page to omit framework and core library backtrace lines. The option is enabled by default. [#77] * The ERB output buffer is now available to helpers via the @_out_buf instance variable. * It's now much easier to test sessions in unit tests by passing a ":session" option to any of the mock request methods. e.g., get '/', {}, :session => { 'foo' => 'bar' } * The testing framework specific files ('sinatra/test/spec', 'sinatra/test/bacon', 'sinatra/test/rspec', etc.) have been deprecated. See http://sinatrarb.com/testing.html for instructions on setting up a testing environment with these frameworks. * The request-level #send_data method from Sinatra 0.3.3 has been added for compatibility but is deprecated. * Fix :provides causing crash on any request when request has no Accept header [#139] * Fix that ERB templates were evaluated twice per "erb" call. * Fix app-level middleware not being run when the Sinatra application is run as middleware. * Fixed some issues with running under Rack's CGI handler caused by writing informational stuff to stdout. * Fixed that reloading was sometimes enabled when starting from a rackup file [#110] * Fixed that "." in route patterns erroneously matched any character instead of a literal ".". [#124] = 0.9.0.4 / 2009-01-25 * Using halt with more than 1 args causes ArgumentError [#131] * using halt in a before filter doesn't modify response [#127] * Add deprecated Sinatra::EventContext to unbreak plugins [#130] * Give access to GET/POST params in filters [#129] * Preserve non-nested params in nested params hash [#117] * Fix backtrace dump with Rack::Lint [#116] = 0.9.0.3 / 2009-01-21 * Fall back on mongrel then webrick when thin not found. [#75] * Use :environment instead of :env in test helpers to fix deprecation warnings coming from framework. * Make sinatra/test/rspec work again [#113] * Fix app_file detection on windows [#118] * Fix static files with Rack::Lint in pipeline [#121] = 0.9.0.2 / 2009-01-18 * Halting a before block should stop processing of routes [#85] * Fix redirect/halt in before filters [#85] = 0.9.0 / 2009-01-18 * Works with and requires Rack >= 0.9.1 * Multiple Sinatra applications can now co-exist peacefully within a single process. The new "Sinatra::Base" class can be subclassed to establish a blank-slate Rack application or middleware component. Documentation on using these features is forth-coming; the following provides the basic gist: http://gist.github.com/38605 * Parameters with subscripts are now parsed into a nested/recursive Hash structure. e.g., "post[title]=Hello&post[body]=World" yields params: {'post' => {'title' => 'Hello', 'body' => 'World'}}. * Regular expressions may now be used in route pattens; captures are available at "params[:captures]". * New ":provides" route condition takes an array of mime types and matches only when an Accept request header is present with a corresponding type. [cypher] * New request-level "pass" method; immediately exits the current block and passes control to the next matching route. * The request-level "body" method now takes a block; evaluation is deferred until an attempt is made to read the body. The block must return a String or Array. * New "route conditions" system for attaching rules for when a route matches. The :agent and :host route options now use this system. * New "dump_errors" option controls whether the backtrace is dumped to rack.errors when an exception is raised from a route. The option is enabled by default for top-level apps. * Better default "app_file", "root", "public", and "views" location detection; changes to "root" and "app_file" automatically cascade to other options that depend on them. * Error mappings are now split into two distinct layers: exception mappings and custom error pages. Exception mappings are registered with "error(Exception)" and are run only when the app raises an exception. Custom error pages are registered with "error(status_code)", where "status_code" is an integer, and are run any time the response has the status code specified. It's also possible to register an error page for a range of status codes: "error(500..599)". * In-file templates are now automatically imported from the file that requires 'sinatra'. The use_in_file_templates! method is still available for loading templates from other files. * Sinatra's testing support is no longer dependent on Test::Unit. Requiring 'sinatra/test' adds the Sinatra::Test module and Sinatra::TestHarness class, which can be used with any test framework. The 'sinatra/test/unit', 'sinatra/test/spec', 'sinatra/test/rspec', or 'sinatra/test/bacon' files can be required to setup a framework-specific testing environment. See the README for more information. * Added support for Bacon (test framework). The 'sinatra/test/bacon' file can be required to setup Sinatra test helpers on Bacon::Context. * Deprecated "set_option" and "set_options"; use "set" instead. * Deprecated the "env" option ("options.env"); use "environment" instead. * Deprecated the request level "stop" method; use "halt" instead. * Deprecated the request level "entity_tag" method; use "etag" instead. Both "entity_tag" and "etag" were previously supported. * Deprecated the request level "headers" method (HTTP response headers); use "response['Header-Name']" instead. * Deprecated "Sinatra.application"; use "Sinatra::Application" instead. * Deprecated setting Sinatra.application = nil to reset an application. This should no longer be necessary. * Deprecated "Sinatra.default_options"; use "Sinatra::Default.set(key, value)" instead. * Deprecated the "ServerError" exception. All Exceptions are now treated as internal server errors and result in a 500 response status. * Deprecated the "get_it", "post_it", "put_it", "delete_it", and "head_it" test helper methods. Use "get", "post", "put", "delete", and "head", respectively, instead. * Removed Event and EventContext classes. Applications are defined in a subclass of Sinatra::Base; each request is processed within an instance. = 0.3.3 / 2009-01-06 * Pin to Rack 0.4.0 (this is the last release on Rack 0.4) * Log unhandled exception backtraces to rack.errors. * Use RACK_ENV environment variable to establish Sinatra environment when given. Thin sets this when started with the -e argument. * BUG: raising Sinatra::NotFound resulted in a 500 response code instead of 404. * BUG: use_in_file_templates! fails with CR/LF (#45) * BUG: Sinatra detects the app file and root path when run under thin/passenger. = 0.3.2 * BUG: Static and send_file read entire file into String before sending. Updated to stream with 8K chunks instead. * Rake tasks and assets for building basic documentation website. See http://sinatra.rubyforge.org * Various minor doc fixes. = 0.3.1 * Unbreak optional path parameters [jeremyevans] = 0.3.0 * Add sinatra.gemspec w/ support for github gem builds. Forks can now enable the build gem option in github to get free username-sinatra.gem builds: gem install username-sinatra.gem --source=http://gems.github.com/ * Require rack-0.4 gem; removes frozen rack dir. * Basic RSpec support; require 'sinatra/test/rspec' instead of 'sinatra/test/spec' to use. [avdi] * before filters can modify request environment vars used for routing (e.g., PATH_INFO, REQUEST_METHOD, etc.) for URL rewriting type functionality. * In-file templates now uses @@ instead of ## as template separator. * Top-level environment test predicates: development?, test?, production? * Top-level "set", "enable", and "disable" methods for tweaking app options. [rtomayko] * Top-level "use" method for building Rack middleware pipelines leading to app. See README for usage. [rtomayko] * New "reload" option - set false to disable reloading in development. * New "host" option - host/ip to bind to [cschneid] * New "app_file" option - override the file to reload in development mode [cschneid] * Development error/not_found page cleanup [sr, adamwiggins] * Remove a bunch of core extensions (String#to_param, String#from_param, Hash#from_params, Hash#to_params, Hash#symbolize_keys, Hash#pass) * Various grammar and formatting fixes to README; additions on community and contributing [cypher] * Build RDoc using Hanna template: http://sinatrarb.rubyforge.org/api * Specs, documentation and fixes for splat'n routes [vic] * Fix whitespace errors across all source files. [rtomayko] * Fix streaming issues with Mongrel (body not closed). [bmizerany] * Fix various issues with environment not being set properly (configure blocks not running, error pages not registering, etc.) [cypher] * Fix to allow locals to be passed to ERB templates [cschneid] * Fix locking issues causing random errors during reload in development. * Fix for escaped paths not resolving static files [Matthew Walker] = 0.2.1 * File upload fix and minor tweaks. = 0.2.0 * Initial gem release of 0.2 codebase. sinatra-1.4.3/README.pt-pt.md0000644000004100000410000004252112161612727015543 0ustar www-datawww-data# Sinatra *Atenção: Este documento é apenas uma tradução da versão em inglês e pode estar desatualizado.* Sinatra é uma [DSL](http://pt.wikipedia.org/wiki/Linguagem_de_domínio_específico) para criar rapidamente aplicações web em Ruby com o mínimo de esforço: ``` ruby # minhaapp.rb require 'rubygems' require 'sinatra' get '/' do 'Olá Mundo!' end ``` Instale a gem e execute com: ``` shell sudo gem install sinatra ruby minhaapp.rb ``` Aceda em: [localhost:4567](http://localhost:4567) ## Rotas No Sinatra, uma rota é um metodo HTTP associado a uma URL correspondente padrão. Cada rota é associada a um bloco: ``` ruby get '/' do .. mostrar algo .. end post '/' do .. criar algo .. end put '/' do .. atualizar algo .. end delete '/' do .. apagar algo .. end ``` Rotas são encontradas na ordem em que são definidas. A primeira rota que é encontrada invoca o pedido. Padrões de rota podem incluir parâmetros nomeados, acessíveis através da hash `params`: ``` ruby get '/ola/:nome' do # corresponde a "GET /ola/foo" e "GET /ola/bar" # params[:nome] é 'foo' ou 'bar' "Olá #{params[:nome]}!" end ``` Pode também aceder a parâmetros nomeados através do bloco de parâmetros: ``` ruby get '/ola/:nome' do |n| "Olá #{n}!" end ``` Padrões de rota podem também incluir parâmetros splat (asteriscos), acessíveis através do array `params[:splat]`. ``` ruby get '/diga/*/ao/*' do # corresponde a /diga/ola/ao/mundo params[:splat] # => ["ola", "mundo"] end get '/download/*.*' do # corresponde a /download/pasta/do/arquivo.xml params[:splat] # => ["pasta/do/arquivo", "xml"] end ``` Rotas correspondem-se com expressões regulares: ``` ruby get %r{/ola/([\w]+)} do "Olá, #{params[:captures].first}!" end ``` Ou com um bloco de parâmetro: ``` ruby get %r{/ola/([\w]+)} do |c| "Olá, #{c}!" end ``` Rotas podem incluir uma variedade de condições correspondentes, por exemplo, o agente usuário: ``` ruby get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do "Você está a utilizar a versão #{params[:agent][0]} do Songbird." end get '/foo' do # Corresponde a um navegador não Songbird end ``` ## Arquivos estáticos Arquivos estáticos são disponibilizados a partir do directório `./public`. Você pode especificar um local diferente através da opção `:public_folder` ``` ruby set :public_folder, File.dirname(__FILE__) + '/estatico' ``` Note que o nome do directório público não é incluido no URL. Um arquivo `./public/css/style.css` é disponibilizado como `http://example.com/css/style.css`. ## Views / Templates Templates presumem-se estar localizados sob o directório `./views`. Para utilizar um directório de views diferente: ``` ruby set :views, File.dirname(__FILE__) + '/modelo' ``` Uma coisa importante a ser lembrada é que você sempre tem as referências dos templates como símbolos, mesmo se eles estiverem num sub-directório (nesse caso utilize `:'subdir/template'`). Métodos de renderização irão processar qualquer string passada directamente para elas. ### Haml Templates A gem/biblioteca haml é necessária para renderizar templates HAML: ``` ruby # É necessário requerir 'haml' na aplicação. require 'haml' get '/' do haml :index end ``` Renderiza `./views/index.haml`. [Opções Haml](http://haml.info/docs/yardoc/file.HAML_REFERENCE.html#options) podem ser definidas globalmente através das configurações do sinatra, veja [Opções e Configurações](http://www.sinatrarb.com/configuration.html), e substitua em uma requisição individual. ``` ruby set :haml, {:format => :html5 } # o formato padrão do Haml é :xhtml get '/' do haml :index, :haml_options => {:format => :html4 } # substituido end ``` ### Erb Templates ``` ruby # É necessário requerir 'erb' na aplicação. require 'erb' get '/' do erb :index end ``` Renderiza `./views/index.erb` ### Erubis A gem/biblioteca erubis é necessária para renderizar templates erubis: ``` ruby # É necessário requerir 'erubis' na aplicação. require 'erubis' get '/' do erubis :index end ``` Renderiza `./views/index.erubis` ### Builder Templates A gem/biblioteca builder é necessária para renderizar templates builder: ``` ruby # É necessário requerir 'builder' na aplicação. require 'builder' get '/' do content_type 'application/xml', :charset => 'utf-8' builder :index end ``` Renderiza `./views/index.builder`. ### Sass Templates A gem/biblioteca sass é necessária para renderizar templates sass: ``` ruby # É necessário requerir 'haml' ou 'sass' na aplicação. require 'sass' get '/stylesheet.css' do content_type 'text/css', :charset => 'utf-8' sass :stylesheet end ``` Renderiza `./views/stylesheet.sass`. [Opções Sass](http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#options) podem ser definidas globalmente através das configurações do sinatra, veja [Opções e Configurações](http://www.sinatrarb.com/configuration.html), e substitua em uma requisição individual. ``` ruby set :sass, {:style => :compact } # o estilo padrão do Sass é :nested get '/stylesheet.css' do content_type 'text/css', :charset => 'utf-8' sass :stylesheet, :style => :expanded # substituido end ``` ### Less Templates A gem/biblioteca less é necessária para renderizar templates Less: ``` ruby # É necessário requerir 'less' na aplicação. require 'less' get '/stylesheet.css' do content_type 'text/css', :charset => 'utf-8' less :stylesheet end ``` Renderiza `./views/stylesheet.less`. ### Templates Inline ``` ruby get '/' do haml '%div.title Olá Mundo' end ``` Renderiza a string, em uma linha, no template. ### Acedendo a Variáveis nos Templates Templates são avaliados dentro do mesmo contexto que os manipuladores de rota. Variáveis de instância definidas em rotas manipuladas são directamente acedidas por templates: ``` ruby get '/:id' do @foo = Foo.find(params[:id]) haml '%h1= @foo.nome' end ``` Ou, especifique um hash explícito para variáveis locais: ``` ruby get '/:id' do foo = Foo.find(params[:id]) haml '%h1= foo.nome', :locals => { :foo => foo } end ``` Isso é tipicamente utilizado quando renderizamos templates parciais (partials) dentro de outros templates. ### Templates Inline Templates podem ser definidos no final do arquivo fonte(.rb): ``` ruby require 'rubygems' require 'sinatra' get '/' do haml :index end __END__ @@ layout %html = yield @@ index %div.title Olá Mundo!!!!! ``` NOTA: Templates inline definidos no arquivo fonte são automaticamente carregados pelo sinatra. Digite \`enable :inline\_templates\` se tem templates inline no outro arquivo fonte. ### Templates nomeados Templates também podem ser definidos utilizando o método top-level `template`: ``` ruby template :layout do "%html\n =yield\n" end template :index do '%div.title Olá Mundo!' end get '/' do haml :index end ``` Se existir um template com nome “layout”, ele será utilizado sempre que um template for renderizado. Pode desactivar layouts usando `:layout => false`. ``` ruby get '/' do haml :index, :layout => !request.xhr? end ``` ## Helpers Use o método de alto nível `helpers` para definir métodos auxiliares para utilizar em manipuladores de rotas e modelos: ``` ruby helpers do def bar(nome) "#{nome}bar" end end get '/:nome' do bar(params[:nome]) end ``` ## Filtros Filtros Before são avaliados antes de cada requisição dentro do contexto da requisição e podem modificar a requisição e a reposta. Variáveis de instância definidas nos filtros são acedidas através de rotas e templates: ``` ruby before do @nota = 'Olá!' request.path_info = '/foo/bar/baz' end get '/foo/*' do @nota #=> 'Olá!' params[:splat] #=> 'bar/baz' end ``` Filtros After são avaliados após cada requisição dentro do contexto da requisição e também podem modificar o pedido e a resposta. Variáveis de instância definidas nos filtros before e rotas são acedidas através dos filtros after: ``` ruby after do puts response.status end ``` Filtros opcionalmente têm um padrão, fazendo com que sejam avaliados somente se o caminho do pedido coincidir com esse padrão: ``` ruby before '/protected/*' do autenticar! end after '/create/:slug' do |slug| session[:last_slug] = slug end ``` ## Halting Para parar imediatamente uma requisição dentro de um filtro ou rota utilize: ``` ruby halt ``` Pode também especificar o status ao parar… ``` ruby halt 410 ``` Ou com um corpo de texto… ``` ruby halt 'isto será o corpo de texto' ``` Ou também… ``` ruby halt 401, 'vamos embora!' ``` Com cabeçalhos… ``` ruby halt 402, {'Content-Type' => 'text/plain'}, 'revanche' ``` ## Passing Dentro de uma rota, pode passar para a próxima rota correspondente usando `pass`: ``` ruby get '/adivinhar/:quem' do pass unless params[:quem] == 'Frank' 'Apanhaste-me!' end get '/adivinhar/*' do 'Falhaste!' end ``` O bloqueio da rota é imediatamente encerrado e o controle continua com a próxima rota de parâmetro. Se o parâmetro da rota não for encontrado, um 404 é retornado. ## Configuração Correndo uma vez, na inicialização, em qualquer ambiente: ``` ruby configure do ... end ``` Correndo somente quando o ambiente (`RACK_ENV` environment variável) é definido para `:production`: ``` ruby configure :production do ... end ``` Correndo quando o ambiente é definido para `:production` ou `:test`: ``` ruby configure :production, :test do ... end ``` ## Lidar com Erros Lida-se com erros no mesmo contexto das rotas e filtros before, o que signifca que `haml`, `erb`, etc, estão disponíveis. ### Não Encontrado Quando um `Sinatra::NotFound` exception é levantado, ou o código de status da reposta é 404, o manipulador `not_found` é invocado: ``` ruby not_found do 'Isto está longe de ser encontrado' end ``` ### Erro O manipulador `error` é invocado sempre que uma exceção é lançada a partir de um bloco de rota ou um filtro. O objecto da exceção pode ser obtido a partir da variável Rack `sinatra.error`: ``` ruby error do 'Peço desculpa, houve um erro desagradável - ' + env['sinatra.error'].name end ``` Erros personalizados: ``` ruby error MeuErroPersonalizado do 'O que aconteceu foi...' + env['sinatra.error'].message end ``` Então, se isso acontecer: ``` ruby get '/' do raise MeuErroPersonalizado, 'alguma coisa desagradável' end ``` O resultado será: ``` O que aconteceu foi...alguma coisa desagradável ``` Alternativamente, pode definir um manipulador de erro para um código de status: ``` ruby error 403 do 'Accesso negado' end get '/secreto' do 403 end ``` Ou um range (alcance): ``` ruby error 400..510 do 'Boom' end ``` O Sinatra define os manipuladores especiais `not_found` e `error` quando corre no ambiente de desenvolvimento. ## Mime Types Quando utilizamos `send_file` ou arquivos estáticos pode ter mime types Sinatra não entendidos. Use `mime_type` para os registar por extensão de arquivos: ``` ruby mime_type :foo, 'text/foo' ``` Pode também utilizar isto com o helper `content_type`: ``` ruby content_type :foo ``` ## Middleware Rack O Sinatra corre no [Rack](http://rack.rubyforge.org/), uma interface padrão mínima para frameworks web em Ruby. Uma das capacidades mais interessantes do Rack, para desenvolver aplicações, é o suporte de “middleware” – componentes que residem entre o servidor e a aplicação, monitorizando e/ou manipulando o pedido/resposta (request/response) HTTP para providenciar varios tipos de funcionalidades comuns. O Sinatra torna a construção de pipelines do middleware Rack fácil a um nível superior utilizando o método `use`: ``` ruby require 'sinatra' require 'meu_middleware_personalizado' use Rack::Lint use MeuMiddlewarePersonalizado get '/ola' do 'Olá mundo' end ``` A semântica de `use` é idêntica aquela definida para a DSL [Rack::Builder](http://rack.rubyforge.org/doc/classes/Rack/Builder.html) (mais frequentemente utilizada para arquivos rackup). Por exemplo, o método `use` aceita múltiplos argumentos/variáveis, bem como blocos: ``` ruby use Rack::Auth::Basic do |utilizador, senha| utilizador == 'admin' && senha == 'secreto' end ``` O Rack é distribuido com uma variedade de middleware padrões para logs, debugs, rotas de URL, autenticação, e manipuladores de sessão.Sinatra utiliza muitos desses componentes automaticamente dependendo da configuração, por isso, tipicamente nao é necessário utilizar `use` explicitamente. ## Testando Testes no Sinatra podem ser escritos utilizando qualquer biblioteca ou framework de teste baseados no Rack. [Rack::Test](http://gitrdoc.com/brynary/rack-test) é recomendado: ``` ruby require 'minha_aplicacao_sinatra' require 'rack/test' class MinhaAplicacaoTeste < Test::Unit::TestCase include Rack::Test::Methods def app Sinatra::Application end def meu_test_default get '/' assert_equal 'Ola Mundo!', last_response.body end def teste_com_parametros get '/atender', :name => 'Frank' assert_equal 'Olá Frank!', last_response.bodymeet end def test_com_ambiente_rack get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' assert_equal "Você está utilizando o Songbird!", last_response.body end end ``` NOTA: Os módulos de classe embutidos `Sinatra::Test` e `Sinatra::TestHarness` são depreciados na versão 0.9.2. ## Sinatra::Base - Middleware, Bibliotecas e aplicativos modulares Definir sua aplicação a um nível superior de trabalho funciona bem para micro aplicativos, mas tem consideráveis incovenientes na construção de componentes reutilizáveis como um middleware Rack, metal Rails, bibliotecas simples como um componente de servidor, ou mesmo extensões Sinatra. A DSL de nível superior polui o espaço do objeto e assume um estilo de configuração de micro aplicativos (exemplo: um simples arquivo de aplicação, directórios `./public` e `./views`, logs, página de detalhes de excepção, etc.). É onde o Sinatra::Base entra em jogo: ``` ruby require 'sinatra/base' class MinhaApp < Sinatra::Base set :sessions, true set :foo, 'bar' get '/' do 'Olá mundo!' end end ``` A classe MinhaApp é um componente Rack independente que pode utilizar como um middleware Rack, uma aplicação Rack, ou metal Rails. Pode utilizar ou executar esta classe com um arquivo rackup `config.ru`; ou, controlar um componente de servidor fornecendo como biblioteca: ``` ruby MinhaApp.run! :host => 'localhost', :port => 9090 ``` Os métodos disponíveis para subclasses `Sinatra::Base` são exatamente como aqueles disponíveis via a DSL de nível superior. Aplicações de nível mais alto podem ser convertidas para componentes `Sinatra::Base` com duas modificações: - Seu arquivo deve requerer `sinatra/base` ao invés de `sinatra`; outra coisa, todos os métodos DSL do Sinatra são importados para o espaço principal. - Coloque as rotas da sua aplicação, manipuladores de erro, filtros e opções na subclasse de um `Sinatra::Base`. `Sinatra::Base` é um quadro branco. Muitas opções são desactivadas por padrão, incluindo o servidor embutido. Veja [Opções e Configurações](http://sinatra.github.com/configuration.html) para detalhes de opções disponíveis e seus comportamentos. SIDEBAR: A DSL de alto nível do Sinatra é implementada utilizando um simples sistema de delegação. A classe `Sinatra::Application` – uma subclasse especial da `Sinatra::Base` – recebe todos os `:get`, `:put`, `:post`, `:delete`, `:before`, `:error`, `:not_found`, `:configure`, e `:set` messages enviados para o alto nível. Dê você mesmo uma vista de olhos ao código: aqui está o [Sinatra::Delegator mixin](http://github.com/sinatra/sinatra/blob/ceac46f0bc129a6e994a06100aa854f606fe5992/lib/sinatra/base.rb#L1128) sendo [incluido dentro de um espaço principal](http://github.com/sinatra/sinatra/blob/ceac46f0bc129a6e994a06100aa854f606fe5992/lib/sinatra/main.rb#L28) ## Linha de Comandos As aplicações Sinatra podem ser executadas directamente: ``` shell ruby minhaapp.rb [-h] [-x] [-e AMBIENTE] [-p PORTA] [-o HOST] [-s SERVIDOR] ``` As opções são: ``` -h # ajuda -p # define a porta (padrão é 4567) -o # define o host (padrão é 0.0.0.0) -e # define o ambiente (padrão é development) -s # especifica o servidor/manipulador rack (padrão é thin) -x # activa o bloqueio (padrão é desligado) ``` ## A última versão Se gostaria de utilizar o código da última versão do Sinatra, crie um clone local e execute sua aplicação com o directório `sinatra/lib` no `LOAD_PATH`: ``` shell cd minhaapp git clone git://github.com/sinatra/sinatra.git ruby -I sinatra/lib minhaapp.rb ``` Alternativamente, pode adicionar o directório do `sinatra/lib` no `LOAD_PATH` do seu aplicativo: ``` ruby $LOAD_PATH.unshift File.dirname(__FILE__) + '/sinatra/lib' require 'rubygems' require 'sinatra' get '/sobre' do "Estou correndo a versão" + Sinatra::VERSION end ``` Para actualizar o código do Sinatra no futuro: ``` shell cd meuprojeto/sinatra git pull ``` ## Mais - [Website do Projeto](http://www.sinatrarb.com/) - Documentação adicional, novidades e links para outros recursos. - [Contribuir](http://www.sinatrarb.com/contributing) - Encontrou um bug? Precisa de ajuda? Tem um patch? - [Acompanhar Questões](http://github.com/sinatra/sinatra/issues) - [Twitter](http://twitter.com/sinatra) - [Lista de Email](http://groups.google.com/group/sinatrarb/topics) - [IRC: \#sinatra](irc://chat.freenode.net/#sinatra) em [freenode.net](http://freenode.net) sinatra-1.4.3/lib/0000755000004100000410000000000012161612727013763 5ustar www-datawww-datasinatra-1.4.3/lib/sinatra.rb0000644000004100000410000000011012161612727015741 0ustar www-datawww-datarequire 'sinatra/base' require 'sinatra/main' enable :inline_templates sinatra-1.4.3/lib/sinatra/0000755000004100000410000000000012161612727015424 5ustar www-datawww-datasinatra-1.4.3/lib/sinatra/version.rb0000644000004100000410000000004712161612727017437 0ustar www-datawww-datamodule Sinatra VERSION = '1.4.3' end sinatra-1.4.3/lib/sinatra/base.rb0000644000004100000410000017547412161612727016705 0ustar www-datawww-data# external dependencies require 'rack' require 'tilt' require 'rack/protection' # stdlib dependencies require 'thread' require 'time' require 'uri' # other files we need require 'sinatra/showexceptions' require 'sinatra/version' module Sinatra # The request object. See Rack::Request for more info: # http://rack.rubyforge.org/doc/classes/Rack/Request.html class Request < Rack::Request HEADER_PARAM = /\s*[\w.]+=(?:[\w.]+|"(?:[^"\\]|\\.)*")?\s*/ HEADER_VALUE_WITH_PARAMS = /(?:(?:\w+|\*)\/(?:\w+(?:\.|\-|\+)?|\*)*)\s*(?:;#{HEADER_PARAM})*/ # Returns an array of acceptable media types for the response def accept @env['sinatra.accept'] ||= begin entries = @env['HTTP_ACCEPT'].to_s.scan(HEADER_VALUE_WITH_PARAMS) entries.map { |e| AcceptEntry.new(e) }.sort end end def accept?(type) preferred_type.include?(type) end def preferred_type(*types) accepts = accept # just evaluate once return accepts.first if types.empty? types.flatten! return types.first if accepts.empty? accepts.detect do |pattern| type = types.detect { |t| File.fnmatch(pattern, t) } return type if type end end alias secure? ssl? def forwarded? @env.include? "HTTP_X_FORWARDED_HOST" end def safe? get? or head? or options? or trace? end def idempotent? safe? or put? or delete? or link? or unlink? end def link? request_method == "LINK" end def unlink? request_method == "UNLINK" end private class AcceptEntry attr_accessor :params def initialize(entry) params = entry.scan(HEADER_PARAM).map do |s| key, value = s.strip.split('=', 2) value = value[1..-2].gsub(/\\(.)/, '\1') if value.start_with?('"') [key, value] end @entry = entry @type = entry[/[^;]+/].delete(' ') @params = Hash[params] @q = @params.delete('q') { "1.0" }.to_f end def <=>(other) other.priority <=> self.priority end def priority # We sort in descending order; better matches should be higher. [ @q, -@type.count('*'), @params.size ] end def to_str @type end def to_s(full = false) full ? entry : to_str end def respond_to?(*args) super or to_str.respond_to?(*args) end def method_missing(*args, &block) to_str.send(*args, &block) end end end # The response object. See Rack::Response and Rack::Response::Helpers for # more info: # http://rack.rubyforge.org/doc/classes/Rack/Response.html # http://rack.rubyforge.org/doc/classes/Rack/Response/Helpers.html class Response < Rack::Response def initialize(*) super headers['Content-Type'] ||= 'text/html' end def body=(value) value = value.body while Rack::Response === value @body = String === value ? [value.to_str] : value end def each block_given? ? super : enum_for(:each) end def finish result = body if drop_content_info? headers.delete "Content-Length" headers.delete "Content-Type" end if drop_body? close result = [] end if calculate_content_length? # if some other code has already set Content-Length, don't muck with it # currently, this would be the static file-handler headers["Content-Length"] = body.inject(0) { |l, p| l + Rack::Utils.bytesize(p) }.to_s end [status.to_i, headers, result] end private def calculate_content_length? headers["Content-Type"] and not headers["Content-Length"] and Array === body end def drop_content_info? status.to_i / 100 == 1 or drop_body? end def drop_body? [204, 205, 304].include?(status.to_i) end end # Some Rack handlers (Thin, Rainbows!) implement an extended body object protocol, however, # some middleware (namely Rack::Lint) will break it by not mirroring the methods in question. # This middleware will detect an extended body object and will make sure it reaches the # handler directly. We do this here, so our middleware and middleware set up by the app will # still be able to run. class ExtendedRack < Struct.new(:app) def call(env) result, callback = app.call(env), env['async.callback'] return result unless callback and async?(*result) after_response { callback.call result } setup_close(env, *result) throw :async end private def setup_close(env, status, headers, body) return unless body.respond_to? :close and env.include? 'async.close' env['async.close'].callback { body.close } env['async.close'].errback { body.close } end def after_response(&block) raise NotImplementedError, "only supports EventMachine at the moment" unless defined? EventMachine EventMachine.next_tick(&block) end def async?(status, headers, body) return true if status == -1 body.respond_to? :callback and body.respond_to? :errback end end # Behaves exactly like Rack::CommonLogger with the notable exception that it does nothing, # if another CommonLogger is already in the middleware chain. class CommonLogger < Rack::CommonLogger def call(env) env['sinatra.commonlogger'] ? @app.call(env) : super end superclass.class_eval do alias call_without_check call unless method_defined? :call_without_check def call(env) env['sinatra.commonlogger'] = true call_without_check(env) end end end class NotFound < NameError #:nodoc: def http_status; 404 end end # Methods available to routes, before/after filters, and views. module Helpers # Set or retrieve the response status code. def status(value = nil) response.status = value if value response.status end # Set or retrieve the response body. When a block is given, # evaluation is deferred until the body is read with #each. def body(value = nil, &block) if block_given? def block.each; yield(call) end response.body = block elsif value headers.delete 'Content-Length' unless request.head? response.body = value else response.body end end # Halt processing and redirect to the URI provided. def redirect(uri, *args) 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" response['Location'] = uri(uri.to_s, settings.absolute_redirects?, settings.prefixed_redirects?) halt(*args) end # Generates the absolute URI for a given path in the app. # Takes Rack routers and reverse proxies into account. def uri(addr = nil, absolute = true, add_script_name = true) return addr if addr =~ /\A[A-z][A-z0-9\+\.\-]*:/ uri = [host = ""] if absolute host << "http#{'s' if request.secure?}://" if request.forwarded? or request.port != (request.secure? ? 443 : 80) host << request.host_with_port else host << request.host end end uri << request.script_name.to_s if add_script_name uri << (addr ? addr : request.path_info).to_s File.join uri end alias url uri alias to uri # Halt processing and return the error status provided. def error(code, body = nil) code, body = 500, code.to_str if code.respond_to? :to_str response.body = body unless body.nil? halt code end # Halt processing and return a 404 Not Found. def not_found(body = nil) error 404, body end # Set multiple response headers with Hash. def headers(hash = nil) response.headers.merge! hash if hash response.headers end # Access the underlying Rack session. def session request.session end # Access shared logger object. def logger request.logger end # Look up a media type by file extension in Rack's mime registry. def mime_type(type) Base.mime_type(type) end # Set the Content-Type of the response body given a media type or file # extension. def content_type(type = nil, params = {}) return response['Content-Type'] unless type default = params.delete :default mime_type = mime_type(type) || default fail "Unknown media type: %p" % type if mime_type.nil? mime_type = mime_type.dup unless params.include? :charset or settings.add_charset.all? { |p| not p === mime_type } params[:charset] = params.delete('charset') || settings.default_encoding end params.delete :charset if mime_type.include? 'charset' unless params.empty? mime_type << (mime_type.include?(';') ? ', ' : ';') mime_type << params.map do |key, val| val = val.inspect if val =~ /[";,]/ "#{key}=#{val}" end.join(', ') end response['Content-Type'] = mime_type end # Set the Content-Disposition to "attachment" with the specified filename, # instructing the user agents to prompt to save. def attachment(filename = nil, disposition = 'attachment') response['Content-Disposition'] = disposition.to_s if filename params = '; filename="%s"' % File.basename(filename) response['Content-Disposition'] << params ext = File.extname(filename) content_type(ext) unless response['Content-Type'] or ext.empty? end end # Use the contents of the file at +path+ as the response body. def send_file(path, opts = {}) if opts[:type] or not response['Content-Type'] content_type opts[:type] || File.extname(path), :default => 'application/octet-stream' end disposition = opts[:disposition] filename = opts[:filename] disposition = 'attachment' if disposition.nil? and filename filename = path if filename.nil? attachment(filename, disposition) if disposition last_modified opts[:last_modified] if opts[:last_modified] file = Rack::File.new nil file.path = path result = file.serving env result[1].each { |k,v| headers[k] ||= v } headers['Content-Length'] = result[1]['Content-Length'] halt opts[:status] || result[0], result[2] rescue Errno::ENOENT 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 # block 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, @keep_open = back.to_proc, scheduler, keep_open @callbacks, @closed = [], false end def close return if @closed @closed = true @scheduler.schedule { @callbacks.each { |c| c.call }} 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) return yield if @closed @callbacks << block end alias errback callback def closed? @closed end 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) scheduler = env['async.callback'] ? EventMachine : Stream current = @params.dup body Stream.new(scheduler, keep_open) { |out| with_params(current) { yield(out) } } 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 # a Hash of value directives (:max_age, :min_stale, :s_max_age). # # cache_control :public, :must_revalidate, :max_age => 60 # => Cache-Control: public, must-revalidate, max-age=60 # # See RFC 2616 / 14.9 for more on standard cache control directives: # http://tools.ietf.org/html/rfc2616#section-14.9.1 def cache_control(*values) if values.last.kind_of?(Hash) hash = values.pop hash.reject! { |k,v| v == false } hash.reject! { |k,v| values << k if v == true } else hash = {} end values.map! { |value| value.to_s.tr('_','-') } hash.each do |key, value| key = key.to_s.tr('_', '-') value = value.to_i if key == "max-age" values << [key, value].join('=') end response['Cache-Control'] = values.join(', ') if values.any? end # Set the Expires header and Cache-Control/max-age directive. Amount # can be an integer number of seconds in the future or a Time object # indicating when the response should be considered "stale". The remaining # "values" arguments are passed to the #cache_control helper: # # expires 500, :public, :must_revalidate # => Cache-Control: public, must-revalidate, max-age=60 # => Expires: Mon, 08 Jun 2009 08:50:17 GMT # def expires(amount, *values) values << {} unless values.last.kind_of?(Hash) if amount.is_a? Integer time = Time.now + amount.to_i max_age = amount else time = time_for amount max_age = time - Time.now end values.last.merge!(:max_age => max_age) cache_control(*values) response['Expires'] = time.httpdate end # Set the last modified time of the resource (HTTP 'Last-Modified' header) # and halt if conditional GET matches. The +time+ argument is a Time, # DateTime, or other object that responds to +to_time+. # # When the current request includes an 'If-Modified-Since' header that is # equal or later than the time specified, execution is immediately halted # with a '304 Not Modified' response. def last_modified(time) return unless time time = time_for time response['Last-Modified'] = time.httpdate return if env['HTTP_IF_NONE_MATCH'] if status == 200 and env['HTTP_IF_MODIFIED_SINCE'] # compare based on seconds since epoch since = Time.httpdate(env['HTTP_IF_MODIFIED_SINCE']).to_i halt 304 if since >= time.to_i end if (success? or status == 412) and env['HTTP_IF_UNMODIFIED_SINCE'] # compare based on seconds since epoch since = Time.httpdate(env['HTTP_IF_UNMODIFIED_SINCE']).to_i halt 412 if since < time.to_i end rescue ArgumentError end # Set the response entity tag (HTTP 'ETag' header) and halt if conditional # GET matches. The +value+ argument is an identifier that uniquely # identifies the current version of the resource. The +kind+ argument # indicates whether the etag should be used as a :strong (default) or :weak # cache validator. # # When the current request includes an 'If-None-Match' header with a # matching etag, execution is immediately halted. If the request method is # GET or HEAD, a '304 Not Modified' response is sent. def etag(value, options = {}) # Before touching this code, please double check RFC 2616 14.24 and 14.26. options = {:kind => options} unless Hash === options kind = options[:kind] || :strong new_resource = options.fetch(:new_resource) { request.post? } unless [:strong, :weak].include?(kind) raise ArgumentError, ":strong or :weak expected" end value = '"%s"' % value value = 'W/' + value if kind == :weak response['ETag'] = value if success? or status == 304 if etag_matches? env['HTTP_IF_NONE_MATCH'], new_resource halt(request.safe? ? 304 : 412) end if env['HTTP_IF_MATCH'] halt 412 unless etag_matches? env['HTTP_IF_MATCH'], new_resource end end end # Sugar for redirect (example: redirect back) def back request.referer end # whether or not the status is set to 1xx def informational? status.between? 100, 199 end # whether or not the status is set to 2xx def success? status.between? 200, 299 end # whether or not the status is set to 3xx def redirect? status.between? 300, 399 end # whether or not the status is set to 4xx def client_error? status.between? 400, 499 end # whether or not the status is set to 5xx def server_error? status.between? 500, 599 end # whether or not the status is set to 404 def not_found? status == 404 end # 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 elsif value.is_a? Time value elsif value.respond_to? :new_offset # DateTime#to_time does the same on 1.9 d = value.new_offset 0 t = Time.utc d.year, d.mon, d.mday, d.hour, d.min, d.sec + d.sec_fraction t.getlocal elsif value.respond_to? :mday # Date#to_time does the same on 1.9 Time.local(value.year, value.mon, value.mday) elsif value.is_a? Numeric Time.at value else Time.parse value.to_s end rescue ArgumentError => boom raise boom rescue Exception raise ArgumentError, "unable to convert #{value.inspect} to a Time object" end private # Helper method checking if a ETag value list includes the current ETag. def etag_matches?(list, new_resource = request.post?) return !new_resource if list == '*' list.to_s.split(/\s*,\s*/).include? response['ETag'] end def with_params(temp_params) original, @params = @params, temp_params yield ensure @params = original if original 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. # # `template` is either the name or path of the template as symbol # (Use `:'subdir/myview'` for views in subdirectories), or a string # that will be rendered. # # Possible options are: # :content_type The content type to use, same arguments as content_type. # :layout If set to something falsy, no layout is rendered, otherwise # the specified layout is used (Ignored for `sass` and `less`) # :layout_engine Engine to use for rendering the layout. # :locals A hash with local variables that should be available # in the template # :scope If set, template is evaluate with the binding of the given # object rather than the application instance. # :views Views directory to use. module Templates module ContentTyped attr_accessor :content_type end def initialize super @default_layout = :layout end def erb(template, options = {}, locals = {}, &block) render(:erb, template, options, locals, &block) end def erubis(template, options = {}, locals = {}) warn "Sinatra::Templates#erubis is deprecated and will be removed, use #erb instead.\n" \ "If you have Erubis installed, it will be used automatically." render :erubis, template, options, locals end def haml(template, options = {}, locals = {}, &block) render(:haml, template, options, locals, &block) end def sass(template, options = {}, locals = {}) options.merge! :layout => false, :default_content_type => :css render :sass, template, options, locals end def scss(template, options = {}, locals = {}) options.merge! :layout => false, :default_content_type => :css render :scss, template, options, locals end def less(template, options = {}, locals = {}) options.merge! :layout => false, :default_content_type => :css render :less, template, options, locals end def stylus(template, options={}, locals={}) options.merge! :layout => false, :default_content_type => :css render :styl, template, options, locals end def builder(template = nil, options = {}, locals = {}, &block) options[:default_content_type] = :xml render_ruby(:builder, template, options, locals, &block) end def liquid(template, options = {}, locals = {}, &block) render(:liquid, template, options, locals, &block) end def markdown(template, options = {}, locals = {}) render :markdown, template, options, locals end def textile(template, options = {}, locals = {}) render :textile, template, options, locals end def rdoc(template, options = {}, locals = {}) render :rdoc, template, options, locals end def radius(template, options = {}, locals = {}) render :radius, template, options, locals end def markaby(template = nil, options = {}, locals = {}, &block) render_ruby(:mab, template, options, locals, &block) end def coffee(template, options = {}, locals = {}) options.merge! :layout => false, :default_content_type => :js render :coffee, template, options, locals end def nokogiri(template = nil, options = {}, locals = {}, &block) options[:default_content_type] = :xml render_ruby(:nokogiri, template, options, locals, &block) end def slim(template, options = {}, locals = {}, &block) render(:slim, template, options, locals, &block) end def creole(template, options = {}, locals = {}) render :creole, template, options, locals end def wlang(template, options = {}, locals = {}, &block) render(:wlang, template, options, locals, &block) end def yajl(template, options = {}, locals = {}) options[:default_content_type] = :json render :yajl, template, options, locals end def rabl(template, options = {}, locals = {}) Rabl.register! render :rabl, template, options, locals end # Calls the given block for every possible template file in views, # named name.ext, where ext is registered on engine. def find_template(views, name, engine) yield ::File.join(views, "#{name}.#{@preferred_extension}") Tilt.mappings.each do |ext, engines| next unless ext != @preferred_extension and engines.include? engine yield ::File.join(views, "#{name}.#{ext}") end end private # logic shared between builder and nokogiri def render_ruby(engine, template, options = {}, locals = {}, &block) options, template = template, nil if template.is_a?(Hash) template = Proc.new { block } if template.nil? render engine, template, options, locals end def render(engine, data, options = {}, locals = {}, &block) # merge app-level options engine_options = settings.respond_to?(engine) ? settings.send(engine) : {} options = engine_options.merge(options) # extract generic options locals = options.delete(:locals) || locals || {} views = options.delete(:views) || settings.views || "./views" layout = options[:layout] layout = false if layout.nil? && options.include?(:layout) eat_errors = layout.nil? layout = engine_options[:layout] if layout.nil? or layout == true layout = @default_layout if layout.nil? or layout == true layout_options = options.delete(:layout_options) || {} content_type = options.delete(:content_type) || options.delete(:default_content_type) layout_engine = options.delete(:layout_engine) || engine scope = options.delete(:scope) || self options.delete(:layout) # set some defaults options[:outvar] ||= '@_out_buf' options[:default_encoding] ||= settings.default_encoding # compile and render template begin layout_was = @default_layout @default_layout = false template = compile_template(engine, data, options, views) output = template.render(scope, locals, &block) ensure @default_layout = layout_was end # render layout if layout options = options.merge(:views => views, :layout => false, :eat_errors => eat_errors, :scope => scope) options.merge! layout_options catch(:layout_missing) { return render(layout_engine, layout, options, locals) { output } } end output.extend(ContentTyped).content_type = content_type if content_type output end def compile_template(engine, data, options, views) eat_errors = options.delete :eat_errors template_cache.fetch engine, data, options, views do template = Tilt[engine] raise "Template engine not found: #{engine}" if template.nil? case data when Symbol body, path, line = settings.templates[data] if body body = body.call if body.respond_to?(:call) template.new(path, line.to_i, options) { body } else found = false @preferred_extension = engine.to_s find_template(views, data, template) do |file| path ||= file # keep the initial path rather than the last one if found = File.exists?(file) path = file break end end throw :layout_missing if eat_errors and not found template.new(path, 1, options) end when Proc, String body = data.is_a?(String) ? Proc.new { data } : data path, line = settings.caller_locations.first template.new(path, line.to_i, options, &body) else raise ArgumentError, "Sorry, don't know how to render #{data.inspect}." end end end end # Base class for all Sinatra applications and middleware. class Base include Rack::Utils include Helpers include Templates URI = ::URI.const_defined?(:Parser) ? ::URI::Parser.new : ::URI attr_accessor :app, :env, :request, :response, :params attr_reader :template_cache def initialize(app = nil) super() @app = app @template_cache = Tilt::Cache.new yield self if block_given? end # Rack call interface. def call(env) dup.call!(env) end def call!(env) # :nodoc: @env = env @request = Request.new(env) @response = Response.new @params = indifferent_params(@request.params) template_cache.clear if settings.reload_templates force_encoding(@params) @response['Content-Type'] = nil invoke { dispatch! } invoke { error_block!(response.status) } unless @env['sinatra.error'] unless @response['Content-Type'] if Array === body and body[0].respond_to? :content_type content_type body[0].content_type else content_type :html end end @response.finish end # Access settings defined with Base.set. def self.settings self end # Access settings defined with Base.set. def settings self.class.settings end def options warn "Sinatra::Base#options is deprecated and will be removed, " \ "use #settings instead." settings end # Exit the current block, halts any further processing # of the request, and returns the specified response. def halt(*response) response = response.first if response.length == 1 throw :halt, response end # Pass control to the next matching route. # If there are no more matching routes, Sinatra will # return a 404 response. def pass(&block) throw :pass, block end # Forward the request to the downstream app -- middleware only. def forward fail "downstream app not set" unless @app.respond_to? :call status, headers, body = @app.call env @response.status = status @response.body = body @response.headers.merge! headers nil end private # Run filters defined on the class and all superclasses. def filter!(type, base = settings) filter! type, base.superclass if base.superclass.respond_to?(:filters) base.filters[type].each { |args| process_route(*args) } end # Run routes defined on the class and all superclasses. def route!(base = settings, pass_block = nil) if routes = base.routes[@request.request_method] routes.each do |pattern, keys, conditions, block| pass_block = process_route(pattern, keys, conditions) do |*args| env['sinatra.route'] = block.instance_variable_get(:@route_name) route_eval { block[*args] } end end end # Run routes defined in superclass. if base.superclass.respond_to?(:routes) return route!(base.superclass, pass_block) end route_eval(&pass_block) if pass_block route_missing end # Run a route block and throw :halt with the result. def route_eval throw :halt, yield end # If the current request matches pattern and conditions, fill params # with keys and call the given block. # Revert params afterwards. # # Returns pass block. def process_route(pattern, keys, conditions, block = nil, values = []) route = @request.path_info route = '/' if route.empty? and not settings.empty_path_info? return unless match = pattern.match(route) values += match.captures.to_a.map { |v| force_encoding URI.unescape(v) if v } if values.any? original, @params = params, params.merge('splat' => [], 'captures' => values) keys.zip(values) { |k,v| Array === @params[k] ? @params[k] << v : @params[k] = v if v } end catch(:pass) do conditions.each { |c| throw :pass if c.bind(self).call == false } block ? block[self, values] : yield(self, values) end ensure @params = original if original end # No matching route was found or all routes passed. The default # implementation is to forward the request downstream when running # as middleware (@app is non-nil); when no downstream app is set, raise # a NotFound exception. Subclasses can override this method to perform # custom route miss logic. def route_missing if @app forward else raise NotFound end end # Attempt to serve static files from public directory. Throws :halt when # a matching file is found, returns nil otherwise. def static! return if (public_dir = settings.public_folder).nil? public_dir = File.expand_path(public_dir) path = File.expand_path(public_dir + unescape(request.path_info)) return unless path.start_with?(public_dir) and File.file?(path) env['sinatra.static_file'] = path cache_control(*settings.static_cache_control) if settings.static_cache_control? send_file path, :disposition => nil end # Enable string or symbol key access to the nested params hash. def indifferent_params(object) case object when Hash new_hash = indifferent_hash object.each { |key, value| new_hash[key] = indifferent_params(value) } new_hash when Array object.map { |item| indifferent_params(item) } else object end end # Creates a Hash with indifferent access. def indifferent_hash Hash.new {|hash,key| hash[key.to_s] if Symbol === key } end # Run the block with 'throw :halt' support and apply result to the response. def invoke res = catch(:halt) { yield } res = [res] if Fixnum === res or String === res if Array === res and Fixnum === res.first res = res.dup status(res.shift) body(res.pop) headers(*res) elsif res.respond_to? :each body res end nil # avoid double setting the same response tuple twice end # Dispatch a request with error handling. def dispatch! invoke do static! if settings.static? && (request.get? || request.head?) filter! :before route! end rescue ::Exception => boom invoke { handle_exception!(boom) } ensure begin filter! :after unless env['sinatra.static_file'] rescue ::Exception => boom invoke { handle_exception!(boom) } unless @env['sinatra.error'] end end # Error handling during requests. def handle_exception!(boom) @env['sinatra.error'] = boom if boom.respond_to? :http_status status(boom.http_status) elsif settings.use_code? and boom.respond_to? :code and boom.code.between? 400, 599 status(boom.code) else status(500) end status(500) unless status.between? 400, 599 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' if settings.x_cascade? body '

Not Found

' end 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, boom end # Find an custom error block for the key(s) specified. def error_block!(key, *block_params) base = settings while base.respond_to?(:errors) next base = base.superclass unless args_array = base.errors[key] args_array.reverse_each do |args| first = args == args_array.first args += [block_params] resp = process_route(*args) return resp unless resp.nil? && !first end end return false unless key.respond_to? :superclass and key.superclass < Exception error_block!(key.superclass, *block_params) end def dump_errors!(boom) msg = ["#{boom.class} - #{boom.message}:", *boom.backtrace].join("\n\t") @env['rack.errors'].puts(msg) end class << self CALLERS_TO_IGNORE = [ # :nodoc: /\/sinatra(\/(base|main|showexceptions))?\.rb$/, # all sinatra code /lib\/tilt.*\.rb$/, # all tilt code /^\(.*\)$/, # generated code /rubygems\/(custom|core_ext\/kernel)_require\.rb$/, # rubygems require hacks /active_support/, # active_support require hacks /bundler(\/runtime)?\.rb/, # bundler require hacks /= 1.9.2 /src\/kernel\/bootstrap\/[A-Z]/ # maglev kernel files ] # contrary to what the comment said previously, rubinius never supported this if defined?(RUBY_IGNORE_CALLERS) warn "RUBY_IGNORE_CALLERS is deprecated and will no longer be supported by Sinatra 2.0" CALLERS_TO_IGNORE.concat(RUBY_IGNORE_CALLERS) end attr_reader :routes, :filters, :templates, :errors # Removes all routes, filters, middleware and extension hooks from the # current class (not routes/filters/... defined by its superclass). def reset! @conditions = [] @routes = {} @filters = {:before => [], :after => []} @errors = {} @middleware = [] @prototype = nil @extensions = [] if superclass.respond_to?(:templates) @templates = Hash.new { |hash,key| superclass.templates[key] } else @templates = {} end end # Extension modules registered on this class and all superclasses. def extensions if superclass.respond_to?(:extensions) (@extensions + superclass.extensions).uniq else @extensions end end # Middleware used in this class and all superclasses. def middleware if superclass.respond_to?(:middleware) superclass.middleware + @middleware else @middleware end end # Sets an option to the given value. If the value is a proc, # the proc will be called every time the option is accessed. def set(option, value = (not_set = true), ignore_setter = false, &block) raise ArgumentError if block and !not_set value, not_set = block, false if block if not_set raise ArgumentError unless option.respond_to?(:each) option.each { |k,v| set(k, v) } return self end if respond_to?("#{option}=") and not ignore_setter return __send__("#{option}=", value) end setter = proc { |val| set option, val, true } getter = proc { value } case value when Proc getter = value when Symbol, Fixnum, FalseClass, TrueClass, NilClass getter = value.inspect when Hash setter = proc do |val| val = value.merge val if Hash === val set option, val, true end end define_singleton("#{option}=", setter) if setter define_singleton(option, getter) if getter define_singleton("#{option}?", "!!#{option}") unless method_defined? "#{option}?" self end # Same as calling `set :option, true` for each of the given options. def enable(*opts) opts.each { |key| set(key, true) } end # Same as calling `set :option, false` for each of the given options. def disable(*opts) opts.each { |key| set(key, false) } end # Define a custom error handler. Optionally takes either an Exception # class, or an HTTP status code to specify which errors should be # handled. def error(*codes, &block) args = compile! "ERROR", //, block codes = codes.map { |c| Array(c) }.flatten codes << Exception if codes.empty? codes.each { |c| (@errors[c] ||= []) << args } end # Sugar for `error(404) { ... }` def not_found(&block) error(404, &block) error(Sinatra::NotFound, &block) end # Define a named template. The block must return the template source. def template(name, &block) filename, line = caller_locations.first templates[name] = [block, filename, line.to_i] end # Define the layout template. The block must return the template source. def layout(name = :layout, &block) template name, &block end # Load embedded templates from the file; uses the caller's __FILE__ # when no file is specified. def inline_templates=(file = nil) file = (file.nil? || file == true) ? (caller_files.first || File.expand_path($0)) : file begin io = ::IO.respond_to?(:binread) ? ::IO.binread(file) : ::IO.read(file) app, data = io.gsub("\r\n", "\n").split(/^__END__$/, 2) rescue Errno::ENOENT app, data = nil end if data if app and app =~ /([^\n]*\n)?#[^\n]*coding: *(\S+)/m encoding = $2 else encoding = settings.default_encoding end lines = app.count("\n") + 1 template = nil force_encoding data, encoding data.each_line do |line| lines += 1 if line =~ /^@@\s*(.*\S)\s*$/ template = force_encoding('', encoding) templates[$1.to_sym] = [template, file, lines] elsif template template << line end end end end # Lookup or register a mime type in Rack's mime registry. def mime_type(type, value = nil) return type if type.nil? return type.to_s if type.to_s.include?('/') type = ".#{type}" unless type.to_s[0] == ?. return Rack::Mime.mime_type(type, nil) unless value Rack::Mime::MIME_TYPES[type] = value end # provides all mime types matching type, including deprecated types: # mime_types :html # => ['text/html'] # mime_types :js # => ['application/javascript', 'text/javascript'] def mime_types(type) type = mime_type type type =~ /^application\/(xml|javascript)$/ ? [type, "text/#$1"] : [type] end # Define a before filter; runs before all requests within the same # context as route handlers and may access/modify the request and # response. def before(path = nil, options = {}, &block) add_filter(:before, path, options, &block) end # Define an after filter; runs after all requests within the same # context as route handlers and may access/modify the request and # response. def after(path = nil, options = {}, &block) add_filter(:after, path, options, &block) end # add a filter def add_filter(type, path = nil, options = {}, &block) path, options = //, path if path.respond_to?(:each_pair) filters[type] << compile!(type, path || //, block, options) end # Add a route condition. The route is considered non-matching when the # block returns false. def condition(name = "#{caller.first[/`.*'/]} condition", &block) @conditions << generate_method(name, &block) end def public=(value) warn ":public is no longer used to avoid overloading Module#public, use :public_dir instead" set(:public_folder, value) end def public_dir=(value) self.public_folder = value end def public_dir public_folder end # Defining a `GET` handler also automatically defines # a `HEAD` handler. def get(path, opts = {}, &block) conditions = @conditions.dup route('GET', path, opts, &block) @conditions = conditions route('HEAD', path, opts, &block) end def put(path, opts = {}, &bk) route 'PUT', path, opts, &bk end def post(path, opts = {}, &bk) route 'POST', path, opts, &bk end def delete(path, opts = {}, &bk) route 'DELETE', path, opts, &bk end def head(path, opts = {}, &bk) route 'HEAD', path, opts, &bk end def options(path, opts = {}, &bk) route 'OPTIONS', path, opts, &bk end def patch(path, opts = {}, &bk) route 'PATCH', path, opts, &bk end def link(path, opts = {}, &bk) route 'LINK', path, opts, &bk end def unlink(path, opts = {}, &bk) route 'UNLINK', path, opts, &bk end # Makes the methods defined in the block and in the Modules given # in `extensions` available to the handlers and templates def helpers(*extensions, &block) class_eval(&block) if block_given? include(*extensions) if extensions.any? end # Register an extension. Alternatively take a block from which an # extension will be created and registered on the fly. def register(*extensions, &block) extensions << Module.new(&block) if block_given? @extensions += extensions extensions.each do |extension| extend extension extension.registered(self) if extension.respond_to?(:registered) end end def development?; environment == :development end def production?; environment == :production end def test?; environment == :test end # Set configuration options for Sinatra and/or the app. # Allows scoping of settings for certain environments. def configure(*envs, &block) yield self if envs.empty? || envs.include?(environment.to_sym) end # Use the specified Rack middleware def use(middleware, *args, &block) @prototype = nil @middleware << [middleware, args, block] end def quit!(server, handler_name) # Use Thin's hard #stop! if available, otherwise just #stop. server.respond_to?(:stop!) ? server.stop! : server.stop $stderr.puts "\n== Sinatra has ended his set (crowd applauds)" unless handler_name =~/cgi/i end # Run the Sinatra app as a self-hosted server using # Thin, Puma, Mongrel, or WEBrick (in that order). If given a block, will call # with the constructed handler once we have taken the stage. def run!(options = {}) set options handler = detect_rack_handler handler_name = handler.name.gsub(/.*::/, '') server_settings = settings.respond_to?(:server_settings) ? settings.server_settings : {} handler.run self, server_settings.merge(:Port => port, :Host => bind) do |server| unless handler_name =~ /cgi/i $stderr.puts "== Sinatra/#{Sinatra::VERSION} has taken the stage " + "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 server if block_given? end rescue Errno::EADDRINUSE $stderr.puts "== Someone is already performing on port #{port}!" end # The prototype instance used to process requests. def prototype @prototype ||= new end # Create a new instance without middleware in front of it. alias new! new unless method_defined? :new! # Create a new instance of the class fronted by its middleware # pipeline. The object is guaranteed to respond to #call but may not be # an instance of the class new was called on. def new(*args, &bk) instance = new!(*args, &bk) Wrapper.new(build(instance).to_app, instance) end # Creates a Rack::Builder instance with all the middleware set up and # the given +app+ as end point. def build(app) builder = Rack::Builder.new setup_default_middleware builder setup_middleware builder builder.run app builder end def call(env) synchronize { prototype.call(env) } end # Like Kernel#caller but excluding certain magic entries and without # line / method information; the resulting array contains filenames only. def caller_files cleaned_caller(1).flatten end # Like caller_files, but containing Arrays rather than strings with the # first element being the file, and the second being the line. def caller_locations cleaned_caller 2 end private # Dynamically defines a method on settings. def define_singleton(name, content = Proc.new) # replace with call to singleton_class once we're 1.9 only (class << self; self; end).class_eval do undef_method(name) if method_defined? name String === content ? class_eval("def #{name}() #{content}; end") : define_method(name, &content) end end # Condition for matching host name. Parameter might be String or Regexp. def host_name(pattern) condition { pattern === request.host } end # Condition for matching user agent. Parameter should be Regexp. # Will set params[:agent]. def user_agent(pattern) condition do if request.user_agent.to_s =~ pattern @params[:agent] = $~[1..-1] true else false end end end alias_method :agent, :user_agent # Condition for matching mimetypes. Accepts file extensions. def provides(*types) types.map! { |t| mime_types(t) } types.flatten! condition do if type = response['Content-Type'] types.include? type or types.include? type[/^[^;]+/] elsif type = request.preferred_type(types) params = (type.respond_to?(:params) ? type.params : {}) content_type(type, params) true else false end end end 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? signature = compile!(verb, path, block, options) (@routes[verb] ||= []) << signature invoke_hook(:route_added, verb, path, block) signature end def invoke_hook(name, *args) extensions.each { |e| e.send(name, *args) if e.respond_to?(name) } end def generate_method(method_name, &block) define_method(method_name, &block) method = instance_method method_name remove_method method_name method end def compile!(verb, path, block, options = {}) options.each_pair { |option, args| send(option, *args) } method_name = "#{verb} #{path}" unbound_method = generate_method(method_name, &block) pattern, keys = compile path conditions, @conditions = @conditions, [] wrapper = block.arity != 0 ? proc { |a,p| unbound_method.bind(a).call(*p) } : proc { |a,p| unbound_method.bind(a).call } wrapper.instance_variable_set(:@route_name, method_name) [ pattern, keys, conditions, wrapper ] end def compile(path) if path.respond_to? :to_str keys = [] # We append a / at the end if there was one. # Reason: Splitting does not split off an empty # string at the end if the split separator # is at the end. # postfix = '/' if path =~ /\/\z/ # Split the path into pieces in between forward slashes. # segments = path.split('/').map! do |segment| ignore = [] # Special character handling. # pattern = segment.to_str.gsub(/[^\?\%\\\/\:\*\w]/) do |c| ignore << escaped(c).join if c.match(/[\.@]/) patt = encoded(c) patt.gsub(/%[\da-fA-F]{2}/) do |match| match.split(//).map {|char| char =~ /[A-Z]/ ? "[#{char}#{char.tr('A-Z', 'a-z')}]" : char}.join end end ignore = ignore.uniq.join # Key handling. # pattern.gsub(/((:\w+)|\*)/) do |match| if match == "*" keys << 'splat' "(.*?)" else keys << $2[1..-1] ignore_pattern = safe_ignore(ignore) ignore_pattern end end end # Special case handling. # if segment = segments.pop if segment.match(/\[\^\\\./) parts = segment.rpartition(/\[\^\\\./) parts[1] = '[^' segments << parts.join else segments << segment end end [/\A#{segments.join('/')}#{postfix}\z/, keys] elsif path.respond_to?(:keys) && path.respond_to?(:match) [path, path.keys] elsif path.respond_to?(:names) && path.respond_to?(:match) [path, path.names] elsif path.respond_to? :match [path, []] else raise TypeError, path end end def encoded(char) enc = URI.escape(char) enc = "(?:#{escaped(char, enc).join('|')})" if enc == char enc = "(?:#{enc}|#{encoded('+')})" if char == " " enc end def escaped(char, enc = URI.escape(char)) [Regexp.escape(enc), URI.escape(char, /./)] end def safe_ignore(ignore) unsafe_ignore = [] ignore = ignore.gsub(/%[\da-fA-F]{2}/) do |hex| unsafe_ignore << hex[1..2] '' end unsafe_patterns = unsafe_ignore.map do |unsafe| chars = unsafe.split(//).map do |char| if char =~ /[A-Z]/ char <<= char.tr('A-Z', 'a-z') end char end "|(?:%[^#{chars[0]}].|%[#{chars[0]}][^#{chars[1]}])" end if unsafe_patterns.length > 0 "((?:[^#{ignore}/?#%]#{unsafe_patterns.join()})+)" else "([^#{ignore}/?#]+)" end end def setup_default_middleware(builder) builder.use ExtendedRack builder.use ShowExceptions if show_exceptions? builder.use Rack::MethodOverride if method_override? builder.use Rack::Head setup_logging builder setup_sessions builder setup_protection builder end def setup_middleware(builder) middleware.each { |c,a,b| builder.use(c, *a, &b) } end def setup_logging(builder) if logging? setup_common_logger(builder) setup_custom_logger(builder) elsif logging == false setup_null_logger(builder) end end def setup_null_logger(builder) builder.use Rack::NullLogger end def setup_common_logger(builder) builder.use Sinatra::CommonLogger end def setup_custom_logger(builder) if logging.respond_to? :to_int builder.use Rack::Logger, logging else builder.use Rack::Logger end end def setup_protection(builder) return unless protection? options = Hash === protection ? protection.dup : {} protect_session = options.fetch(:session) { sessions? } options[:except] = Array options[:except] options[:except] += [:session_hijacking, :remote_token] unless protect_session options[:reaction] ||= :drop_session builder.use Rack::Protection, options end def setup_sessions(builder) return unless sessions? options = {} options[:secret] = session_secret if session_secret? options.merge! sessions.to_hash if sessions.respond_to? :to_hash builder.use Rack::Session::Cookie, options end def detect_rack_handler servers = Array(server) servers.each do |server_name| begin return Rack::Handler.get(server_name.to_s) rescue LoadError, NameError end end fail "Server handler (#{servers.join(',')}) not found." end def inherited(subclass) subclass.reset! subclass.set :app_file, caller_files.first unless subclass.app_file? super end @@mutex = Mutex.new def synchronize(&block) if lock? @@mutex.synchronize(&block) else yield end end # used for deprecation warnings def warn(message) super message + "\n\tfrom #{cleaned_caller.first.join(':')}" end # Like Kernel#caller but excluding certain magic entries def cleaned_caller(keep = 3) caller(1). map { |line| line.split(/:(?=\d|in )/, 3)[0,keep] }. reject { |file, *_| CALLERS_TO_IGNORE.any? { |pattern| file =~ pattern } } end end # Fixes encoding issues by # * defaulting to UTF-8 # * casting params to Encoding.default_external # # The latter might not be necessary if Rack handles it one day. # Keep an eye on Rack's LH #100. def force_encoding(*args) settings.force_encoding(*args) end if defined? Encoding def self.force_encoding(data, encoding = default_encoding) return if data == settings || data.is_a?(Tempfile) if data.respond_to? :force_encoding data.force_encoding(encoding).encode! elsif data.respond_to? :each_value data.each_value { |v| force_encoding(v, encoding) } elsif data.respond_to? :each data.each { |v| force_encoding(v, encoding) } end data end else def self.force_encoding(data, *) data end end reset! set :environment, (ENV['RACK_ENV'] || :development).to_sym set :raise_errors, Proc.new { test? } set :dump_errors, Proc.new { !test? } set :show_exceptions, Proc.new { development? } set :sessions, false set :logging, false set :protection, true set :method_override, false set :use_code, false set :default_encoding, "utf-8" set :x_cascade, true set :add_charset, %w[javascript xml xhtml+xml json].map { |t| "application/#{t}" } settings.add_charset << /^text\// # explicitly generating a session secret eagerly to play nice with preforking begin require 'securerandom' set :session_secret, SecureRandom.hex(64) rescue LoadError, NotImplementedError # SecureRandom raises a NotImplementedError if no random device is available set :session_secret, "%064x" % Kernel.rand(2**256-1) end class << self alias_method :methodoverride?, :method_override? alias_method :methodoverride=, :method_override= end set :run, false # start server via at-exit hook? set :running, false # is the built-in server running now? set :server, %w[HTTP webrick] set :bind, Proc.new { development? ? 'localhost' : '0.0.0.0' } set :port, Integer(ENV['PORT'] && !ENV['PORT'].empty? ? ENV['PORT'] : 4567) ruby_engine = defined?(RUBY_ENGINE) && RUBY_ENGINE if ruby_engine == 'macruby' server.unshift 'control_tower' else server.unshift 'mongrel' if ruby_engine.nil? server.unshift 'puma' if ruby_engine != 'rbx' server.unshift 'thin' if ruby_engine != 'jruby' server.unshift 'puma' if ruby_engine == 'rbx' server.unshift 'trinidad' if ruby_engine =='jruby' end set :absolute_redirects, true set :prefixed_redirects, false set :empty_path_info, nil set :app_file, nil set :root, Proc.new { app_file && File.expand_path(File.dirname(app_file)) } 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) } set :static_cache_control, false error ::Exception do response.status = 500 content_type 'text/html' '

Internal Server Error

' end configure :development do get '/__sinatra__/:image.png' do filename = File.dirname(__FILE__) + "/images/#{params[:image]}.png" content_type :png send_file filename end error NotFound do content_type 'text/html' if self.class == Sinatra::Application code = <<-RUBY.gsub(/^ {12}/, '') #{request.request_method.downcase} '#{request.path_info}' do "Hello World" end RUBY else code = <<-RUBY.gsub(/^ {12}/, '') class #{self.class} #{request.request_method.downcase} '#{request.path_info}' do "Hello World" end end RUBY file = settings.app_file.to_s.sub(settings.root.to_s, '').sub(/^\//, '') code = "# in #{file}\n#{code}" unless file.empty? end (<<-HTML).gsub(/^ {10}/, '')

Sinatra doesn’t know this ditty.

Try this:
#{code}
HTML end end end # Execution context for classic style (top-level) applications. All # DSL methods executed on main are delegated to this class. # # The Application class should not be subclassed, unless you want to # inherit all settings, routes, handlers, and error pages from the # top-level. Subclassing Sinatra::Base is highly recommended for # modular applications. class Application < Base set :logging, Proc.new { ! test? } set :method_override, true set :run, Proc.new { ! test? } set :session_secret, Proc.new { super() unless development? } set :app_file, nil def self.register(*extensions, &block) #:nodoc: added_methods = extensions.map {|m| m.public_instance_methods }.flatten Delegator.delegate(*added_methods) super(*extensions, &block) end end # Sinatra delegation mixin. Mixing this module into an object causes all # methods to be delegated to the Sinatra::Application class. Used primarily # at the top-level. module Delegator #:nodoc: def self.delegate(*methods) methods.each do |method_name| define_method(method_name) do |*args, &block| return super(*args, &block) if respond_to? method_name Delegator.target.send(method_name, *args, &block) end private method_name end end delegate :get, :patch, :put, :post, :delete, :head, :options, :link, :unlink, :template, :layout, :before, :after, :error, :not_found, :configure, :set, :mime_type, :enable, :disable, :use, :development?, :test?, :production?, :helpers, :settings, :register class << self attr_accessor :target end self.target = Application end class Wrapper def initialize(stack, instance) @stack, @instance = stack, instance end def settings @instance.settings end def helpers @instance end def call(env) @stack.call(env) end def inspect "#<#{@instance.class} app_file=#{settings.app_file.inspect}>" end end # Create a new Sinatra application. The block is evaluated in the new app's # class scope. def self.new(base = Base, options = {}, &block) base = Class.new(base) base.class_eval(&block) if block_given? base end # Extend the top-level DSL with the modules provided. def self.register(*extensions, &block) Delegator.target.register(*extensions, &block) end # Include the helper modules provided in Sinatra's request context. def self.helpers(*extensions, &block) Delegator.target.helpers(*extensions, &block) end # Use the middleware for classic applications. def self.use(*args, &block) Delegator.target.use(*args, &block) end end sinatra-1.4.3/lib/sinatra/images/0000755000004100000410000000000012161612727016671 5ustar www-datawww-datasinatra-1.4.3/lib/sinatra/images/500.png0000644000004100000410000007452012161612727017713 0ustar www-datawww-dataPNG  IHDR93_<tEXtSoftwareAdobe ImageReadyqe<xIDATx]`Tn&^w{ ދ4) *S>|T UT PSH޳Ivӳ73fSDdY޽;޹3g52}̰pAOcJ}mMO#;*P|{ڨ{KZZzZzFO;-[ fL:+"PkgzfZ)/ ++zSPpwJY! EE%=*uzf&Jhcld⌍suv666b.NFFZJLLv={ GB'Ne*-=Sظxm\6c4"5^l.MMMBnS侾Vk팎Ψ"༝SJ(Plml{sxmozXzJ7`5::y775c [>"%E!jM 1 D^.l&wX`^w{F67C]t X.c"[[`g|V:kt]nPm Szbv~#JKԚx)՘2[Պ:BQ* :=X)U~~"qX#!C{Z\';9czwUUUi/o{S tW^ݤWg$&%cU؃՞rX{@P*?p``@?Ņ:{ϫBY!hk};biGƐn|hB}w}#+*x.?GFs;L%l#؇yƠ?om8pT*577&Aw47ݷo9h*-κu7o)x4555H"8;975Ҁ7ӸZPTWWݸuUW[n @lOwث{u… T h6h`üjt܄@mUYcex׷ReeqPP6l+v|E`O\X=ܹ 24֬}\ ]jyYf̘R75579;SR:TPmlXJ\WW}nۺm3`CCB{l=1A. ]{fgv}Dsʀ +,X|gfecZml$m|juy`UX  Rإ~r\Vffd|g˗-ǒΨg>M6..P B"oo/.y"6@QWWd:}ly}͚NU%իfffn*JeC##: [H-? zkPM&_cڷ:u NŠ8dIMM`RhZb N AгƒHD~wbbbiAQ^^zKuQٴy P܊S&Ma{s.ɖ:** eJ dq?zsK-j;ɉX ["zܬ ݼd'z]Vܵk#F Cݭ[m @ +<~ӟ>^ Pr# HcJKQ(ŵn.0zqA=Aիg1(33ٹN:q8++'|J@ +:pGUWWWg\&#+x_vGO} tw؃I׮GW Ej\|}RV3*+˗-Cf{ʃWffnr,efevT^~}Tju`PPtTTFFƪUoleEE4:~4Fǝ .44t1ZWS}Q@PwLc===t~`kkc8""'AjFf&jܺy@\z^K~U &󛩡uZ>,,uWy}͚Аnj[Zxذa'=eia߯TR%pwol35.Ne2Ш~r`tJXꦒA9pffV[tC/^2f>Ǎ`ckǢ|_Oz7Ngķ~,w oD9zK89( 8j` X{;;:.s%[.c$ 1cgbͮ]g30;~;U(+YhCfWW} @8E#[eegΞ/..?~Q&B83WDn3ʦƀ2%N)f͙6̽3&R3fJNNΎ^r՗۷g2 2޼Аjee%dEWW'3?ɇfGܻ'WB&Mܰ*K@=G --,]nWrqvAZ(. V~R{;wIaEdWq6,<,,,}63Kf-n[&Yss{ +ōLxV*+ 50* Hfd!((g*s1?c"G0jl&vG{;;%,Z@ B89Jd_IiI .`erA'OSgsr2fðhNJMM6#}OPڍ=CH@% _ZZ6x䤤/TTT;_Nv/\+3f㊟llbb  >iiK/^TZV4N~~UkPw~Z)i U!^_ldK(eSBBTp1cM7bU"&kkk-eTNÇ5657UppzgNHČAu9zZrrRyy3\|GLk?)>럠7_pqm6涶vzzv^^$pir޴y nL>ؿf7(,>Aso$xQ߿D}LTa ރc6?YW_7Wp%KrIOO7n|w0n߶ի22nkt]^V^]V9 8quqʁ LgV5***:*j }g DʪМl`@555 fvvvZҡ<XUvPaaMtҬWL⋫\oDEu \#7lCE%!W/_o/%?pщ'vIQv< Z(+/c1Xw4?iVlر^3U5DC-'nzFftL,o^~dukT0ʼyO@x0+\An޲e$<-t0==}-opG2C`&Q|HZ}>%$$B0b5ûwm[!#`ZrrsG͒H@Ya(հ'FСỚn+8mC8ah`PYY YMÇ :d0+#Aov1[>ilh|W==<؅|XBBիW,-%݄MkAd|z/l YuXBCB5ה)!}e&3Br4X$;kk׾j:L"@w"^> zRo03g='vc*g׺ `2xHVv}m]-Ύ  H$Rŋ=,hnf= zڰa˯@#G~;sٽȘDFuuu8`Y`1sY#3А|}<|8<9)ۛ*И1n„uBɋ:[oρMݛ;HFf&T*4B]T6 .>c&>Bz% MSꫯ_7L;[;SSO?pYf/[W-N2ySfV,WLGCj~8;EE MLLpdII)6\]]H^Qa!1n5ܠ)% .ZuႧGwB0 ?ӚbMSFFA@* lmm~W.O O~>f̘UVAR/ݻ3:KLS֫\m:ת;T+jcSIՁ)+$: ^lI|UVӂs"*mٳgx f'b:B{+KKʚV z"h=Æ ^k}vEJ֊Uhl 򁚚dÆdgB9|hpP]tI'g'X7666ϏZnڼϿZlOCa^\U| 80>>OT$)(,Ğ`(%eZ!'><y7M.щqcС}ZdbFզee-wv v/**jU5ޡ]q@։exy_ >&á-Z[q$ .܊km))VׯϹsZغ0} RRÞDDp1Ϩ酋~;uj옱_BXYYsVJJvݘ7SĂVeTeiU%9d21Զw(R<=%,͝]\ݓ >|> 4mRv׷|tGycetZ9+/5N4oM]Ȉ͒g_&sԧᓻw:k;e1` K'AN@VNNQ̛$_zz_!(sξ:-iӦ .B5ꗓ?gddL4񹳁DTy,Wϼ{W5?`UD"V=i/v {eee_ZblqA'Nt|TTa^}啥KhYowp=x`ȔН|?rnܸhB\"TC7mBuY[Y:::`~JXNzA=Mͷf1PNfi÷~1c F^xiȎ+_JUoh>&:Z{ց֡ gּ:W:|5pK{UUSPmU9eWJKkgkl3K?;c֜"w:l81>8>>Wy ZeĈZmrJ6K524-hD::r+a62Yx;y;:8e .8~uۗf=s\TFf˖IHGU] x ;nܸX@ |Wm˯:mڴq ѫ *mN@bظYCwqvuMAGff|D" lCqs Ǣ6LwIL2-[8K/jz׋=N K. ?Rfew، P NR_t8in]RZpn.*(T~~Y3~0)]S..7m:t0{?õkW,[`97~acx!34ٵoկot*mݶ=o1=a2ΌgXXZq; ā]٫gVq;;[{*@憆mC)Rӎ? =0|+577z톃"%%*:Ĺ^~eh]=䲹snC~JKKVgϚjjjp۶= Ė+(0%vll}Ԅ].[ɖ-8Q V~ x|7 kpMȲr}}8xp hɒe;2G zRŋraJw?k׮KhÇܱ㩧eӃ/17o'OvwϸHs|}|KuR{};wG;_PEFYY8^vV>655746$(LF^vźzz=?efehT>%d _}<~9<-5UqJ3.t{ ϛ>;g0zz@XZZ999}M*. ݻ׉cG::BX l 5'7/005ᇰ! "@+J`Ey!!!U$')$/(xrϜi H#N@K Ȑ3p|}ۀ~}^x9][l[-;Q~ SS`|,GE[X;:jsrA;6<nPpTt:?vL|BbtL,K3#dffEEq7cѽ ֭[7tȐ.txW^]d1cOrR@{jmm " l:͛7?gϞw~%]_vرcqtp| <Ɔ?w7mL?zT,MIIiZNǴAA!ph@: -1pԆ&M\\\JJ#GzhK(xH'"/_5c @ss F_NLJRii..rQ2f**-w"GGDl u,A^^Jĺ>^nn Ix hl ##/5ʩ#uq1$xĸVTL[6oݺ yӦ͞5vNlDD(+FF^&\ƍ|V%c'}ίGiY a#y55d 5J4]s''w]UU]WA9~XvU01 ˌgsOf9 v Č@Ý@jD,-a~EJ3 4[cc v- ]{SHC_I&st;f,>>+23I/ѶNCFFK 9~cǏ}4kL-R[ Q ZO|(<l=8~V \UFz;*))m-"ҘU &chEt$+;2Ma2r@}IojnĭY[Y_\w>x}v4=^(ihj5gff~͍d3TPRk(2x 055-L.)-߻V[s{JTTɓ'KtX(oaaQRRrfV3Ѝ:oXb@&%ki$[ &D/H@p(`m_Aqj251!5\!8}#!y}~8<<\j*UVV8t8nOA Můjx}͚Rˮ]|='ʁDtZ$[j[U{z)!t4 +==8)R  l+)FUUH:6p@^~ХiɄR\RKn⎄z;a(̫/۸-/}\v i-x$_|\ۈLBr!77&5 ~t;4'( n5j5l&&,2Ꚛf5 k;M$tFKo8 PuHBn'[_3SٰgNrP27ƎbrCj*33U+WIY@a \Mutww͘9 #!SxxzwV5 Ѷ~`˓,6qԩ76S$TEnkPRY>| jccHGY{a^9I)%t~\\?ggW,&:4;%%eA_d2SS&A XRZ+)!qv8gC^7bp{[2׎lmE|ʹ.@ӃQY;;|te2G{ [RZv[ZVW@AԁcO>r4?/7tL+e|HM+*.UA-__>|GEիW` efeePe= B߈Zx[o60Ќ>yS__Hh複ǧ5c݉Uh?^~ >yڵD:\*KVTyf5  R+^]]]eHO_Άp*9,h>jtIqQfF(fMmMz&M $Bt\@_%5.8i5Xe# s\Ĺsg?l(upǯȁKBɽzzei[eqhgdhJe%,U)'tƕ|ʸĤ̄tri) M4ǎ;^v̙ۇsg'O~'*hfw +ތk2 ?sLLtLVVր)~m $]YYaF*uiYL" ͥRh[2QI<ތy9˦sM4 R/qvvhyrEZڕطt7^>  !▴DΙ3P32<*F~ RkkbUUI@tuh*fF5 l*,Tbg -FZݡ(jkL{pG^ޕU$Ϙuӧ۾mM#tq6 Z{%ža  @"==} 4ԐۖkC XtMM Do$.|Gc?  Mk׮G2ڷmf[Ĺ?88:ǎxR 6 6;'rȰWuqЅ,JMNbl$s,hKK!Gago{Y7V_áiBupY@&]y'm_865Va:} @!K!ؘ貒}{ܷ-z8`bb:>9)HNX 9t<=߻wd?~ N~H@6 bKKKaM++aqvqym0r"/s `8,App ɟ>sNGyd̘1wb-Mh ?f&off9MOKπPD7[VLqSV.\aϟ}>|ذa;;MBqG33 uuu;vL߾ޞP l=1 8$A$Rnmqi̡EC.VVWʝfΜ~$<+@;駹G z(>@ይ&`tlڼ%%E- i V]a4svvm/JO=bӫMRR|||ѧs 4W$:111II*9s7HToo$~*(Xt_0DSvBvxvq lj Y9P?üyL:gR :2@g%0+X@dm:2G}Jt(m{gt8%_Ԣq*UuHIk6dz v-477eSI IZj8HXƆhQK s68%b l(9Z[ZU( 20::k~{9qԢuYYxzd4KbVţTv Һ~;vAvt?|r$46K{;&kn[V~؉RRRѣW<"#/DGCTE*x^Ot Z Mܙsne0b Z@hX5kEiNe `MD"e*[`AVv6$ӟytoX[Xx{={6t [ PX7zi±2Ezy'U z{z@%{Y qPGXYUH›\mo?ߒҲYgd8Pb 54挌L@TYD!kmŒG),ܜ7|ovߦǏ߸acjjzQQ5b1ݒ㞰:b3FjӰ~0-wF^O?1۴4'''][]T*UC}=za}}=hPÆO$v׽n$BTC3izu#/<~\X.6isfB;Ᾰo 1`g{k& CH7z Mzx$Gj))..|mY[TPoldL˫Id>t/u/fd.n'j"˱V.Ȣ`g}s[k$iE0s6i9Ùp2SHfXei)VQadY/uJ\W^E?q|a oY:u*IqPuzWFfgi:D"1Yȇ3tŊVj3p/hF FR~47##qG M&I5ZdYq:FMndD1}LmM3|lW^ eFNHLضuaBB|Zj*D҇7sБ¿ VKK(Ӗ}OOHܦ&s}A3B{}esa~՗xNƍóJII ?g:/ IFSd,,Pqvls\xk6- ='7=Kqq !6-dg,+//n͊Jm߂zp6= d=lei1aɓ*ږM[٧^͛tY5efU*u\|K/|TD `*6b QBPpȑ#LLM *A#$e !Cԭ+bqNWC81adhn<*)+YlmpLHJA@gJ@{irz]j&L  ͤ$>0-DVרeWb?/P++7]uRÈK'UZB]~Jk赠_8mtTT޽]]ӸcȐ]k@oa(veLte@}^m~z~)** B& T¢$zNwz5j8WqI)Ө l=lQ ssU,P@lchpCd~6"Ӯk&f3.z2h} ۞n4B jooWwla茌L\ I)͕ 9A~*;͙]\-SMKB)ʪ*1ijU5Qc>3O@sX@RY@4<3}B&(Ȁ\cC^n^Bb48BGeBnqdˑ[ò ^OKO÷~~d}?8$$El_ifU~MLMKJJ!KKJzc L. <޸n'."k!xR%iLBpg`T: e?,26iNec ,A)k[YO~&"5kLMlRo%e2`J j;QmTE@naU2GJa'rA _ptx xuh/$ΎZ888[RtJMl U%ecj"پ};zǖbbյx8A}H+w{vO |s,T oieYN2''UJqTDu&pb@ٴ4Ч/Q#-oc#:]Uwfw2m hu;oi ^9:Ald5$ː  $%E$x*,ΖuVHf3~hBPB'wcQ0ХٹoLk)|R`[‚47i4hތlM\x>T8}VU{K, 3T6hN !⼼@ gSV aG/H2Q(Ylԥ }|OKMm ( ;/ce$^p%Z06JCv#Fx"s8=#Wkog$ҨD'd(DA'hHAZ:]xQ@)T ~|.caG(JϤޚ9cgB;::x~Ĥ #005]@I(3?W_yQں#Jw`j%իBMZZO}޹;?t3:رÍ1 \k.xy^Ub"@: 6+GF#-?8&^}=60VNM6#3`Ylༀ.h+j肎bcE\\UT#@:$7otrrA 7_/ׇ0n̯iqq .ZIι^)4THQYSjU ^^YYi{b+w1؄=/7ѣFY$0YGVv; -5ow= vY@nrvZ sۯ ?R$s#98pw9~G=At*~X"16,**8G0alZZS^[qRXTnJ%=3zPu. BBѲD:M>>NN1(ՒR`9 n_S[ـF d,]tt.nԉHK6Cr"T ь%x`CiYoN76t`q3)t%A< xD&4\󣉡׮^ewGV(׭{饗9rЕɪP::.6] NyYĸ% MwBˆv}KRWߎ9QC}izq%# JZZqi@JEPiBب#D`57ep9e^^΋jFAyzyڦ*R ;Gڃc \Yt3g̚=g6Z 'Ht t 𪭫ݴg# ˪-[ ]{$ `N0\ >ஹ'USS;dq'kKr;F{8y /+&Ji;B?2k^>znތЬ>SC9Sgg@hVV;Ux2KKKc:f]Tbwi:87m 00[uCu;_pgb6fgڻ5c$|1[pq%pb~Ckk4`P벲r///! 5TgN49Ye}LtOl:ǰD~ӝ4Q⤵35UaEs׫lڵVVUUzk*M!CXdeg}hO/S']I_'SPX*XesrێQ5%57 4,)i?Cf?;+NJLݺ01K~4:&4_YPZZSҶTjJ+TjO6lbb 5 9HH n"5"XԿ-8jP :y}۶ F{䑩aΠhw۫YB`DJ|++kt*~<u^ֿO{>ea{~3g4onnGe39_WOWG>+ ,FZW[t'{#yLM(UNcտM{ EDD|MLLܿ::ذ"*bJ ˪FNJL G>?>o6 Pd2Z{d2LEzzm]8HcDЀ^xTp Dֻ(55j=*/7el)չsãdr]}ĉ VAdgoSRRnccCb3<4ls񱪫Bi[Mb[=m<AELiKlnK(*.ׯ_|٢r~4<}  ; 2599%1)aP2NRT^p&dC`Gv 4!H.͙scnҥ&6mxٲsqcRB9:4ퟱSu"DX\|\EܹsPz7n\gR{,-+W s2!qK7ܱ?јJM5 j3JKKKH%G+!T*59Uܶmaaa ":5:K .)H8OFgLJyOݼyPOL >}yt̘qڴZ2JV"/]HU~hl]m}VV5SS$O ۸j}[hr#+$׮KBZq ucyzzceʆ(12"KX <#x UU]m;9;:[ ?ʠA===Iba*''fj|s׃2[ixTTT3^.”7C !! VC:Z<@*c%?/7OsΖLe^fz2իe'*))9_d&Wyp ~ʄmŵjjVIvz-H8d" 5Ya*S~ /e{X%~uux07RRtL.sssLLJ[KYYbVGv\Qvva>jɄF_&| :ˋq-jЎ?94.+7_ h ?_ihE3Y׏k`2?,w׮]C7Ead>'!!1e 7o_Ͽ(,'989fl?{ā!pflrrla^DM{5=|! > hhGNYv?S~JJ/ ;%FSw1\h环+wzVFgɎ2uI.eE_u| $듹OiQRY@gg|=|b>rYyq :p;P1gJڨL! 100.w>PaLR)hC}=>/-ov F%պ6Cׄuiϟ7x`6a+;'FTԞ=}ퟘ.IxK|rtڐ VeLtDS&O9{Tz2h zhuIKKCgDV;*ܾݵsw 0uƖq'wr?[[DT6YOti&]=]۸̌Lo~:_TTxR)bc`.2yvɲ=-jM{`{*zU5RF;Y~dnWuUuBB c wHʪ̬i VhTt x(5v/j2ad<_ ҝHMM`^vF 6==qlB(7^Mp RPX}O/J&< ! md,9rY@ bhh>(!![JޫVxyz:oߞ6\ $S`NFRV*-ꢠdZeXk羨@x qppn1F #ۺ"̙pqq7!NT+9&`}k\-_7-z~Q&wuqKRj_zeNEr6'ۆ4´䀪Pܲ;w~Ut!pSSsyY_ODG-;6oޓee.\bƕ@:CnذlJwImCc#cP`dr̘1UZӆ1L,F Yfm `dgTʙOhm~ST1LVM2'CHzZs[LtE 55 _\\dh!C@@ jNW([>Pɨ 9C|::%%Mdu¸v`R|:ᅦXRѩ@5L'(>Vt\ pi] p4˴V@8nc8G #KKx?kogE :RWT%jb2Y*i}k.e!ߗa=vsT}v*qTV!8]me[5~P rssms>VVP g*B㔔s{g9Ms>Q;c D^rr_ .N^tvqZs;Z?boӹ ܿ^~P۾}>LJN@躹3Ո1獍 6tX`PD"lc1A/hn#[Έmjh'wU"+J]HAM2CLE`xHb:Eш6 ˙GsV ;x#9ey?aI@L ٴyKzz:{:s -~՞[\ut`};)[}`[Z+1g=2%dUOO? Lzw^յ׭nn0 qAV( ?~(4'PaN=zȑ#֪ Vb)UCussa5!!AWWX=z^0;iX:tKبB6c@o[[(괰4+EVESyiF\@-`J+8\oͬ3wrlS?#, l3;d)?4Ʈs66*!JJ T@1l@*5j+R[!ɇJRQ5mIZC>cǎWxn9瞻fތa-w9 @'or!4GHf ]25 6ܟmzzlWplx*{ H g@ Q 򩩩a2pIk֬X(Rqf#C`ڥ{۝pl`9p||A_Փccc;w:žϿϙ66mtÍxǯsΎ~sp7O?\{{>ٞr/iժ1lS_0;WI"WA6IEi+"*8Лr>D`lDLf5i#We r*2M`?Cg?`Q4j2ߏ2jSxbbE@%|;o8u:+=YϾ޾6nhR4]r[wD>ϔ:ݏ; KK5t]w=A=vWÿ޺ulܺm>H\3:33 s}yjӌOA+ 3::|T7 .\*OU8 N{0,3lQ"'\ez.h+e QXM5ÜkQb6n#/u2 0r`~ o@~;Fٙ3g@v^Ͽwoyǽ;?0*/Vu|u5"5gj+c8zVYߠs}/9rg;* Koa͚5WOzo=R# 7nq8) eXŕ%hqsz'VmUuM=e]^kfj&*28k{uC8w||"om[m")f5ȱAu3g?(NKUd&zۉ'N8cGM ^g)fw`+KV;|~mxe׮]l7ͣǎN۷jrՖ[-FGccZ DX֪_ 5ǨܰNݤYl'E`fr!A-HQˎgojgy6A#5)υ ^}?uMWr+tCVrt T~7}_WTxVɓ'v o֎7=̳JD(:KK *o#b:NUkgUQ< |td$؏]2X+ĥŽcįv{yeuV9 ZofW5(X>{7O?7:ګWĶfg58CvOL8mddm@ K7or@g޺uc_ufS֭8[ܫd{z E7Q_xXFcPjYV4S =iM$a/:oY:98>5=WqzˏU͇HieE4X_D#Û#bv':u_yGT"vi"/tMz+؍;whܰ~=[nY>$-fQx\c+ئ /6HZ Md)'A@7-3c9פ@{HbOĖPD`\^Bn:E6X (VUt\uvj&YԪU9^J?Idx1G'q7?^ 3讵F  pȑΝoM6{w2qL :x-኷lG@t;4nff`i "9FRFOlА[$ΪqE ,Z8!T3JB$r`ăUy+H-"ۭwwܿ+Ъ۱ v{0]N't?u^cjχ E֪ (.pǾSFّZ Batt4_GZ< OxW$Ds`- E'Of7;3;I~<`1V6|RA`ٳ䲹eAWDR  mϵL1?93DƘ Z8b0u9 Rp:t /qȶUd5jw U \!R^|饷-[4z& fRJ>%]?/~0==ቓ|^ijU4Ns=7;{~@ث6u<_v-: ոʣ&`L=cG{ƱĹ^'uc lր_8\%p" teJE5d3l^&.V2] N FcܠB[JJQm5#T}JG/'yos?O1VM[vj{9 >y1GF".SN|zڢY*pu/yaR4zKK4ӯLfKɈ|.a^]>qT$zlh7Ą2cf' @ gN sܺ|&ht,#XY\l4:|v FGN'Xb4C^^* SSZok[QRlnnwߙEUV7:pv|r$۶oi{D?}&}LKƁwO~Sucu%lJT(Aыi塨(pBJGjTM.7۶kd6TĐY-~ - 6B6V ߺxq.OcPgNX9A{I Y-!J&­SJl׹; F2e>h`C ~%HmU:kݰUǏ{1pxL6J•3x70[ua' v,hqƞl:u,qYiSVE]<|%bؐǝHyXgpJ1ѹs믙fw@ j0N9I};ڵzN!kbfvf5]D3«P,.X0P؆;} V *V6-jxS3 08kWCltaoAp' eYaN0u*u K(L6zBQ=i(j$Y8F 2[F` k;h- YIk gϞXz5Nvu] f|m4k]yt]/W*ex,\gf'&&ݕHY2GQU\@jް>~+%Ag@<c'@]bAZČ*Xr)AC 0MǗ| I8 @RdKY`pmL@`OLf. Z X1+FLa'NΘdJZ׾_;Vը./B`2Raf^i&+1<%T6iHUX%_ k;QUDG=R.Gwάco\?<~C 5KS9]J0Dc\Si(Yvu̥YRca(Y o;ef7ɐ5kc\HshH8̣%_2p&:7RvXNM'հ ?B \JA^đYJ ئo(023%<ߑSN]Y Pcc@V06U1- | 04Zq*hLy:" "̝*X ~]ܸb֛38c.0[͖-W,3h Q4*2@x`9};:/Oy}ZðٖYҥ 0Ab?r "Ê im,+V}ׂ/#( (_˚{|k쇭ͼAS|!k&cr'gffȶFƕ$W>@utj'ǤYp 8]ԫA2$f ̵H`>hk5$yJۼEL8\_4BL2Uƈ9u;D=v"]\*9 GL;ݎaf(oSReނ(jKX`E-4ƀ̍CLMH@nS[u+GϥjɕY]oe<-- %GN4V^0f?}Nf~Eb\vUYJRg !uxxM:31OHf|y-Q1VL7-y(A5!2+<ŠIuKd@opZdBα@cY:i!nDrKXw<ֶYHZʮa턈2+D9JJ3\~>) (;<9 (p *0^@ˏUA^Taw3i*R^b*41;hdq H`5nYDwqt1 WFơ0t'+xlj/Mv 2;\(* HH*T *\{9ZRS]R 69W]69Ǩh ;+P29rUP! d]@ 3XRpxF6ΚMOCsYzAP3Jxʊ6>uxFr;5V=0v[ vࡵ|K}BR -L,UlнPVQ*}hǓ`4KT*ZthTv%`( eK5[;Th\#a52u9Z,$^JE)cNp7*)PYG옎K6qUnr  yO<-rg}GAT]鸡.VNj9=W VY7*~ZV:1fQPxS=G Ш]3]G^ƽi[t,4,÷27̧/>ttGPjU $Cĝs t)vz$=pc0C2;$Bv@mcrXlObEpK8TP*|"14i)8\A 1KN!JTE v 8V )y7 3}]" sMdCcP="sr#uI3yEm=,o0Gx@fi.'?^|@eZbp YL- 8خrKvV:VZ&zoIZ8:d{˵;ԱWL 5dԂ\vieFkhDR`,<)NQU㾮 RԳXK] b pVev Mmz8?S(h-AZMWE䘢L+K.EJ=xΕxkۜ=w+W2<VPbG 岉[$P E d^* \^J` YhٻHfdݗjI;shIjxj\~SpZGW}1W+^0r۰JA2N|z][|M?2]Eu׀"ʫĹ.jE|WNq'-Dp;MБeYA*X!ܨ,]G$C6} \-% wkiUZcYg[%ԁ#V] Ǫy"n*4$wAbQx \ظ(;qOs&^w5ꢡO~%w9*3.7zvk%;(|"Pf ew s~v<8jʤNefKX9^EpJTZ}Kc )b>60UG};۶=S\/;ש&x,zL蠨?I5d\]<\O%Bx<`T/\4+}B´ ԰/R:ؕ/hᾐIq]__'Mq4"]rdCV&!wmL4Rj۪wӉu]]$Q8[alcF ~Nbz)iBVrZIWB߁6Q_^^ã`/}`HzՊKAAfP˂;"b.WXجP2"x^OK{mBiڬG_.BZsfceГ}\x1ˆե3| +MeOiO413~/ޗߦ1ͮr\t/Ξ8E2B4Hh-yIJx*2Q|׊\I>,@T*J5FbuN^{mO_k}uUQd7{ˏz,IWN ]Jt+jҕb5]JWt+])VӕJWR+]JtXMWҕb5]JWt+jҕJW`ت3IENDB`sinatra-1.4.3/lib/sinatra/images/404.png0000644000004100000410000005541112161612727017714 0ustar www-datawww-dataPNG  IHDR,)҈tEXtSoftwareAdobe ImageReadyqe<ZIDATx}[Wu檪soզn$؀m%XCa2HL%HQiv)`mz!7rL4NNNOT w@8#NqKXd.|Ex8pr;AWpCgS H⟴ܠpe8B]7D), ĮZ\Q -\ï_=BU҅8U's$.n{FjiOK, +la*Xu${XkQԉV39٣}]͈rOawXYl‹bn#;{׮Y#/~ JE^pr&eVvF,;-h E&g5-\nl"CDvn|m y°wOeq؁PpG5S9} uSSSFB 4?S{aV̑-!pUxxoeQ{ 0h\b_:8ÃL4S:%;BwO zĻj{Mhoocց.P)v,AZ1gzAOGM"Dhwua!/?2:KxS1.F`e#JF:f2#=Ƨ.zA\ 6=EL ?:߉X쑒IlQV$$d O8;Z|\uoSF~hte\:q'F.Q4E62:hfA >p.uCOG}:^" ~SSS8I"^1%B,^IFU#6@*x~~UL,HRY=zMt jvC|5 %+K#'o-Ya UaaL֧_q%wm$/Kj -Nӭ#5L}F?o/n)M6-)n G]͘Ti[laCqQN/@<qټdǢ_jpqx{7`%7\#Q&[BLF>Bц  iч{^~x`zzo̺BШ}Dh*qqywAqKZP꧴)zľ(l"+Jiib1??/]p;s~镕w|;mZMS |qu2L=!Ąct 54}Zb$ I2NDK9\/pv#!ۣO|⯿= __ ׾?~_דah,a6<gR[Gp&p_{|.G-cE׷+%p\~S@t_g>{ՕշW^y53X>z蓟䣏>z;iO{@55 :dlHgde(_DldZc?P?e~{.@o{(q_|ȑ#O<; ~ipPٟ=zɓx6km8#Y b 5F:;bfPP9m[B~3 :U&kc3Eo^xgy?y_z̈́cz>>}?saq?s~̓{21Ѡ7"QgtphT?r gL\=iu?ŤEk*a 7پ>~\ytŕɉ~ێ--->yɳgϟ;w^8{^|_ Fj(R.nJ$\4"xހbg w GEn%Hz~N~ŁfCih}8qБ~ߛ:p@9/,]|k現_מ~^{}۸DM%"k6[-x1+2>E !^&f L)Q& ^G =rdzίcxo}pر^w 8{pVkF0;c¤hq'"?;>bM{ڒ5Z AQgr[O^ûCl\/}]w؃oɫWɩ`鵵RKq1bT\ଉըT^.Wuz4  mK(ZF4H~MO#"HP_j2W+/_ R=T*pw1!Q Z3jϟ鵫^{{⥋ᴖs|0.?bӧOs_H 'OSv . >A>rY} h%yB2ZB\CV51 7s^v-zҥs/;ҟ>zHKϝ;]wDŽ𘓷ҡCn u662B|=AC)q>a =ީ,stDGqQw#ө={lCvҥsH93Ąk]p:xOiܣ41JdptZ*Ğ!;H<BmMNfpeef4 O ;xFٳWV,--܏i}Pk\Ey0[6?ťEh`~~><ŋٙɉcǎo0Nń\@KSKKk$AĞFj1egf@9s&X+;9y&ׯ=ܹsG!IHFLt&C5 9bP EjHQ1HK<$_OLNb97ë:1 LNNHs:?oGcJf繂8X@hֲdgŎn'rl`c_vkwqG O:ĉI_]|gx??^IYe}g /#Ɛ;FpU=W7cr"e>j2^kya4!Ь:<> ?~ߥG ax~]z >"JG6ѢUi4iJ-7z:MeaG!-G>ͣ`+[2?n;|Cgf8Ϝy{{[>xC۟7)kXP5c=4,~,L"1QD"?g\?Mq9wOoo~_?vyAKl׻zc=[a'FylҨ HB:N):_h{'q'f8zN flHg'O_Easyx7?<dKy8$碦/jX$^2ejz[s=Xkg,̦^`9:$~Ǧ Gs# n]WHL8ȋ7yC{km"Tt 8ǠPbVoƄG[1xuGyo~spAYn'[>l(pK!Q{qmĒ}O1u!S46ʐ#-7ȿ2ښH;}G?Q|//8Ѐ'}LOF:A4f37f`U[v3j`D6˩4a"V0Sd0~? |c?BA?L{g 0^{e LjYXfLf;S4v4qbVkJCCiE Xv' kIjshnȡgϟ_ԿkИ%AdWM5__Q&E[ԶFHETOI!E'0޲V'2|eͶv댼j#)}v "=[`T⸄N|&X`Ih̘h*Mg3&R^j//鮈R:x[B#Uj˚loM0y$ebE-͈M*BPaei=Vq&l̟0IN#9It.yb$<ld 紌*wNN<Ԃz<Ka<^gigXcz3ՙ63udGqk wyX #{^x6oپ$W} SZ{܁~VTtʼnصLj9o1!{x7(+2mi(jGvQV˻=wO3Sl(侉p"T\!WTv,C+Fb9J јԲO4;` {j &=_‰dEo^ل?e?FCeUH߱ɩ~'沍ӹ=5xrVD]@l$ ,NG>(n/E+ni~ԩoXBx$_yB$[N!zݙm 4["? AR)7.!e;A /iRGz(0lit9ECQX5t)k^\7PS7lM¬OI͑ZׄE42I{G5gDBd)&)t(󮩒8P^Q)~Z|#ȋm%͞B 6FL |.Z-=; BtLe'Ms̀82dm,cDZ1郇bLeN(:?Q&'ifH n|F8$T4Esijj^6脁woLMM& ٴA@@X @"f#ZWf;vFq?5e/YvZҍЖ6*Sx 'P+'^7-d_r'H6F &-"XAf 1x-sn8lP_XBSt;)%l3(2r@/bOSލ m !FYp X9]Wxw'\C@ϋF;:WŒŹ}gsbTxwH) ^^Vc jۓQb[~N6gh@TZa-ZФF?Pl5f)fHIn6@ȩy"OcFnPH9eZ:MGrr>Wp̖P۱}ã0DyMt7OҰ~f͢1Li =H~I56nQ%x͈3I'k5MU{{lSL_ۮ yZ.E:i WVVU0zdf 5PjPy=I*7=+LQwg;jI/,Ifd E5N,zGmeS:9.3BBʆM56MQz@u#ݍwV{؟B= dЌ"2#jM K%Ӫz iC15>yCI8΢Z2SQUOp&{΢&h - ~5~QO(thg{o_g_؎\)ucAYQ^|břٴ>ZBCCIlAJqRMgS'LZY XKTV>pg!l˕f =%X ɀ 6p1_9&ҠOgSHHPK{ HDfEJQYc8ឭ1f, n9#NU,l._.FJNę*;^{ k͜VL9}15h'>S"e~#R\3:b7gyb+VɖBd|ӪY9 SKb3){e#͑nιA3 /w/A8ƲWd$;]iP.Sl|^gzYȯuf$y XDcs$٠HdQo}bb44ytV<2uH*DPR, x>M6йpSB>,\#ԻyB.ng!a5Rښkow]Y8)6κ 8Ah.ޤ}ưԭ- ow⿂XJ@f&֣y1qpm$dKX }T2ΰlkkkN̩F9&p٧/;AQήǢV,5;=e@vBO90А}!fTSmںs~m>NͧmAvZ(Adrk]j:_ K Q4-z4_8ff1V7l3Sb|=iEE3cxiy@JI\(QI;yZem& s/YpY_ǥui^ۑ^.?aY}<..mg~gh=y<:g٭^++׮]_^ZjzJE4A6CRpwOxh2~=;x(ߢ5'*WǏoꫯ/]=Vɍ+#csktSRM+,%s9d1}Ϋ^~Y,/~>}=}ӧYdEvAIBL7_(,{.bJ[5^уv[jlG)"%S"3[r T%f,xD=RsE-K%$ßx[?P^f4^M/)A-/m'\0Kq y[kӯݳu0l[ƍUV;{T]{;i[r-;Jh:Jd-F+M_F3XIyN~-{Ekh v020A* UN̊~`ɠù}dEP Y%2bX =DkMԪ}y+6 8f]>KW֡TJLLQdCgH#.G{fZEz2:vӢz""}dqB1T!6`,}{k$P,H1YFFyڸ"n%\o~ϲY]d\o}J]k13_Ev(&T /8EiPoϠebI E--s -)Ri)ye{6S[햐WÈiqlJM+ ONR g@XDEA2KAS'kRi/OX9;f_Z$) Kw2$@Of.#Ȫ>*oVAάf e+%6,YX? 't'N5λ}MHdhҌ,mz(Ir΢:$z6nٗSWFJ/j[Blٲ2Y7<ōEF_)<1]T¾>ζ7uMF\L0`* go~b.8ܤ)v3}OZFŵd2ưe^MȐhͲk ٨,a*y"-a5j~zNhm_3_{wczax#G9+.ePVM&+]>h9שpcNHLj`ÒFd3S 1L *6,3*yFWle.qql;xRW@D;J.i9B@{z]_P{(5{".[|0g|$!Ap[DXu6Wq]"i@zI;yE"^L|'-zVFJ˶Y~oğVz 5.6tm0m#RG~icJ J8i{QgĠ.R%qfRՓhxFBI[s=6 aL@vg4fbBJ"Q@`gv(K]\*J^a[?FވQ2 fvD$fiNRpiCCt ֙A]Ð/2΄!ϭMU&wh_(lghrdfY隥4VVw̫FE~IƟ1EQ4S5=G55:666Ef*mDv*O)s=荈6>mb1E$HvQ4 ¸*f@VDiqɺWs<#WR5-m$"`V%rjhǙY_Q! ;dƻߤ CKuhOLQvlq(ҧAh6Z8L!R#9hӋԸ C~NcdӀ(&bm"ǣ$=IP|ЮXyqd Ch|0]-*jyFXQ_1ah4擸8(NfxtA>;,*'b*E-=۞hOPCzt^,kAgY( E5絋Cqi18OFhL( o2ɧ+,qgNSЈu u(,q(| m”iҝQ"lZ<_z|#Z=Y8{)hkd4chL>hgQCҜ%}hSvbS&/[m[YBvĄ<).g)KBkbG-HLCF;KEl`!`a{L ؅0k8 4%IM) S~vG߭nN 1lEPǡp{j``21͌ ZC15/;ZFE4B$ۘ0O(/1c#X2\HWwk}ˮaIܠqى /eY.RzJ &׭xPZffAv%$~Qw: G1?xzi{ z~B;#^^%6--C*؊%4M**ص5¶fJ+ۏ9 at'!1jlPᓳaEb`Yhܞyh ?ٖG4F-񫒕v=x3Rì&`۞h<#5bMbYl# ~DN@x*O[a!k *T1%owjb5♲ui?ń%Ķ+jsC(}(ւW( <+焓|y+,4C_,c)0zGMdwI#7"Ulrv',!d@i0J9ɸڬ 8Gv\g,N1S#6̣Ki3qN2^ۈ;U;zc6S?ȴ&{Ďpt@z nL=?7_cRWE0CXl#,sf@7T.~as'd-F?8E|2 JbzZbɀVCUP#,!VP+R5⩊a֟p^:bQ:YB3YK>BPcO= BZ1wlՂgTl5<;l,VOt4bh7ô:gQN%u|iG{{= ոAN`XםɫL]gŕ Kf&HDt4&uM %3h=~?^:; _8KcpA Vd5Z<$#4Usa=YͻEcA(l+H!'*6#Lu, qU!Hy,c@VYh !DjDE0ΖAWԔ:)[o(Np ƧgJÏՀ9md%LEa$yHƟ\&͎Lr@ɜR?(ь߿58Of0nn G=c6<>QCQ6Lz6m)au F{&&#W$h #j(F $HBx;5m%{Ǚy2Y |;`N<;IzVULlKh,$Ym&s N L笒K yqӂR6h jCm%Gn %8'$vKIWb2B_bSB"=QΚeVuXB"24ȕ)H'caCդHׄ&;RuWT4{+p4E*ڈɱaguY¸c mLƸ Zh%䤝+u zi4 iĤ{ ve[f/8P%L{4A,腣ɄƳn"c 'atX-wnF{xlvϬ)77҅,G#O%znN,3fz%0\D<ʘ4G#QLR=&TT1:LҳIy:e"ZȴN*h eKz{;#3:F!v ,NA`6o;ٶ=^L,mjiDz:XxIS+$*SƩ7[NNM4nk4h!G8 kiI`i~0ÆSqjggU\(; @.Ba,Rxt3|I_4h'zF&Ë"#+G/nE"P y 'sK,dzOB^äv6%lyRWPa s3ZsA)̺@]$PpA#rA%2%Ga2)uZEPi.o|- kZ@aAr 8᎓ ($ I>; 6FKBp1 w*IP}1Cי:r1Y`n2q'uPvSX@v^-lTxWEGtA[e4JDU+cDcERD/][IqV>fmm^O%, ) FDŽv>=40Lmwޮ/TT"sH e"٤I܅_xbmI ӲaX>Btۘe?[~"b߸H 1t1b֞ ֈ=̄7RMȭ\\ />wzzzff&paa!$AD+^ns .l}^hEQ3`xd-"x =.E"k{p;^^pLS&8o4X"ol&B%QdN,u kxkע>U4E?ܾtR>K:tǟtBeᡵ oO>a[ 0/.p:ѿ"<ּ p2a{W ܙHp#Cljwaw߀1K6Z-b#8H4a]64.:vX@N5l"{c nG^^9)a-sqHOǑX(<8>)҅W ZZZFZxm O'J',܄ $( _v&IȘG@8hL  .v6|# ®i` &0~qw7<:\\=3[[[O8Ln*őm\αJW>Ç Wu o^@ a ,擱q!֑eC*fjT˱oĝr/9ayEn\pyKY o;E]_'Y2좘#:4c|:)}cM0T1xI!ɼ%[T l yMǘ!)px$_q1ܐϰR#Z ؕ^h`St=[$(FÂ߀gDL .u$i]O @"''j&GgX1O֌?2NJp68鴝` ˹i"c1\P̈́x<(%2ۛ܋ LB3\f-bP;n\ *a8G02E,#oy*1P4(Tao7Qs;wPf EK0{oz{0ZNM䴱n71q?1:;K6'1#ԑԴY1_p~K88EE0n4qxrgLpu e 9=M4>ؔ%@&;G٣rqD%v3R%t[XrTF3lKT_+ ֦ qBdH(CK|,A"^ A MK51HR&6mS%%Nd-İ`8Je:D +LBTH̪{&d:?Bb)!JDJ(b@؟Lqщ@L0zhX;l'pd^$!S?u|RO' TMJ:PJS1DqhPhUrN≉,@uppr KmaNix&D )vdB ~ BH3I5|K7 Dx5whcNT&Ole{L^MA=lC-vV]]1njCΎrjQ,Y '[3% ḷjb$Nf:nY]]Y]]K{[%KKKXEW?(U~ NiZVX'! h荒FKh/'uxNvV<'xeRpr/hDr#lu F$kW;w_pjM=ígq|SP:=ݟ;0wwuӧO:5B!1 A;O7,a 6=8k8M4rB359fAB!K&"OqKjuX␽D`Hrk\M8XSPOpL?qzӛ8:, _}+O?~tzO|ɩɉGy/4=$<7U!8"> S[܌lowa;V4ޣA*c8'65`Jxc7vW&.@R: zʡÇRP {ǟ}_~~رcԾo>szo~~7Co{{ 1E_ꫯ|4kЇ؃G&҉ixzy}ApAx8p#X 5#7%<Q\zԬu4׌24u#UbAi@婧?sݯw?~vXb~;~\yǿϞ8qCG+߁s|ǻΞ}Ok}$Hh0ݓ'"f{SDzipO!&Z*R z3X‘\/!mD"'lа[\XOE>FY@HfԨFȑ#ٿz_ɟ|0XX00w_?r'Ϟ=ɓwqW_t}{te[ oÇ X1nK.C4KUb (5.a;YvR*5w1`I}Q2]E$)ڵko( {}nT ¿pO07ia`SS?;v[@P" GpΜy𡃿p/a =8 (K6-*D[BFb7ohG_޶F I 8i.&Ͽ_Mo|S٨Is 뛝YX FaY]~!  ĥa3gJ4XFU-|m)UYN$ߏ&?EUT&XTrpC}ڭEP!(CDrlKٳ'N?q}nz2fKLMeõ>sBTs[>x ִMlG>կ>??lDK2R]YSJoFv ,^a|6!""ł+`T Gw3\_LӇ򗿼euQ,?DVojeup&@p MQX1 p܁= ƇCt੮=:~6j+735br{ԍwA:1M3Ii(r!/X.qUᧀ9}-:~OM K@p`jmPѩ^ ! [k[O0 @ Ma [[}gϾođ! v-Axשapa%X|#Ho!qMZǢ5cZz2D>@Yc4J<ׯ_tiii1!im}icݭO#`fy 6(I7Mz&Pb&/\pʕ ͟<*Ne-a)&̺ LyqM54\XXXZZxG>׿aań(' zTޭ!qIQ?vGj[ZRjEU J=qÇ?t AaP81T^Mf%tH׆q(\z|&#ybRmE>~Ѐ+(q=oCHHjy KKS[z06uP<٨r}4ݟ`ԍY C<"13-a~`-+ѣoK܇q@鱾¿2iohTcᇓq1akpZl_J*/^|?O?SVb~6'Bؔfbv"){~|4\L㤱p""&6LS&oir49#W/cf䳫W9sK_?3/_k15{`6f@V`AsMozG><{(B@U; Sa@x 9iƐm(ן?^+%:\ 1M|;/+ro}[ׯJL/\n>}:>=; e errors, env["rack.errors"] = env["rack.errors"], @@eats_errors if prefers_plain_text?(env) content_type = "text/plain" body = [dump_exception(e)] else content_type = "text/html" body = pretty(env, e) end env["rack.errors"] = errors [500, {"Content-Type" => content_type, "Content-Length" => Rack::Utils.bytesize(body.join).to_s}, body] end private def prefers_plain_text?(env) !(Request.new(env).preferred_type("text/plain","text/html") == "text/html") && [/curl/].index{|item| item =~ env["HTTP_USER_AGENT"]} end def frame_class(frame) if frame.filename =~ /lib\/sinatra.*\.rb/ "framework" elsif (defined?(Gem) && frame.filename.include?(Gem.dir)) || frame.filename =~ /\/bin\/(\w+)$/ "system" else "app" end end TEMPLATE = <<-HTML # :nodoc: <%=h exception.class %> at <%=h path %>

BACKTRACE

(expand)

    <% id = 1 %> <% frames.each do |frame| %> <% if frame.context_line && frame.context_line != "#" %>
  • <%=h frame.filename %> in <%=h frame.function %>
  • <% if frame.pre_context %>
      <% frame.pre_context.each do |line| %>
    1. <%=h line %>
    2. <% end %>
    <% end %>
    1. <%= h frame.context_line %>
    <% if frame.post_context %>
      <% frame.post_context.each do |line| %>
    1. <%=h line %>
    2. <% end %>
    <% end %>
  • <% end %> <% id += 1 %> <% end %>

GET

<% if req.GET and not req.GET.empty? %> <% req.GET.sort_by { |k, v| k.to_s }.each { |key, val| %> <% } %>
Variable Value
<%=h key %>
<%=h val.inspect %>
<% else %>

No GET data.

<% end %>

POST

<% if req.POST and not req.POST.empty? %> <% req.POST.sort_by { |k, v| k.to_s }.each { |key, val| %> <% } %>
Variable Value
<%=h key %>
<%=h val.inspect %>
<% else %>

No POST data.

<% end %>
<% unless req.cookies.empty? %> <% req.cookies.each { |key, val| %> <% } %>
Variable Value
<%=h key %>
<%=h val.inspect %>
<% else %>

No cookie data.

<% end %>

Rack ENV

<% env.sort_by { |k, v| k.to_s }.each { |key, val| %> <% } %>
Variable Value
<%=h key %>
<%=h val %>

You're seeing this error because you have enabled the show_exceptions setting.

HTML end end sinatra-1.4.3/lib/sinatra/main.rb0000644000004100000410000000233012161612727016673 0ustar www-datawww-datarequire 'sinatra/base' module Sinatra class Application < Base # we assume that the first file that requires 'sinatra' is the # app_file. all other path related options are calculated based # on this path by default. set :app_file, caller_files.first || $0 set :run, Proc.new { File.expand_path($0) == File.expand_path(app_file) } if run? && ARGV.any? require 'optparse' OptionParser.new { |op| op.on('-p port', 'set the port (default is 4567)') { |val| set :port, Integer(val) } op.on('-o addr', "set the host (default is #{bind})") { |val| set :bind, val } op.on('-e env', 'set the environment (default is development)') { |val| set :environment, val.to_sym } op.on('-s server', 'specify rack server/handler (default is thin)') { |val| set :server, val } op.on('-x', 'turn on the mutex lock (default is off)') { set :lock, true } }.parse!(ARGV.dup) end end at_exit { Application.run! if $!.nil? && Application.run? } end # include would include the module in Object # extend only extends the `main` object extend Sinatra::Delegator class Rack::Builder include Sinatra::Delegator end