pax_global_header 0000666 0000000 0000000 00000000064 13603175240 0014513 g ustar 00root root 0000000 0000000 52 comment=a4dd24add24f2dd0e7299b9e68e12038138294d3
sinatra-2.0.8.1/ 0000775 0000000 0000000 00000000000 13603175240 0013322 5 ustar 00root root 0000000 0000000 sinatra-2.0.8.1/.gitignore 0000664 0000000 0000000 00000000267 13603175240 0015317 0 ustar 00root root 0000000 0000000 # please add general patterns to your global ignore list
# see https://github.com/github/gitignore#readme
.DS_STORE
*.swp
*.rbc
*.sass-cache
/pkg
/Gemfile.lock
/coverage
.yardoc
/doc
sinatra-2.0.8.1/.travis.sh 0000775 0000000 0000000 00000000706 13603175240 0015252 0 ustar 00root root 0000000 0000000 #!/bin/bash
set -ev
echo "Running sinatra tests..."
bundle exec rake
echo "Running sinatra-contrib tests..."
export BUILDIR=$TRAVIS_BUILD_DIR/sinatra-contrib
export BUNDLE_GEMFILE=$BUILDIR/Gemfile
cd $BUILDIR
bundle install --jobs=3 --retry=3
bundle exec rake
echo "Running rack-protection tests..."
export BUILDIR=$TRAVIS_BUILD_DIR/rack-protection
export BUNDLE_GEMFILE=$BUILDIR/Gemfile
cd $BUILDIR
bundle install --jobs=3 --retry=3
bundle exec rake
sinatra-2.0.8.1/.travis.yml 0000664 0000000 0000000 00000001361 13603175240 0015434 0 ustar 00root root 0000000 0000000 ---
language: ruby
dist: trusty
before_install:
- if [ "$TRAVIS_RUBY_VERSION" = "2.2.10" ]; then
gem install bundler -v '< 2.0';
else
gem install bundler;
fi
- export CXX="g++-4.8"
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.8
- pandoc
rvm:
- 2.2.10
- 2.3.8
- 2.4.9
- 2.5.7
- 2.6.5
- ruby-head
- rbx-3
- jruby-9.2.9.0
- jruby-head
script: ./.travis.sh
matrix:
allow_failures:
- rvm: ruby-head
- rvm: jruby-head
- rvm: jruby-9.2.9.0
- rvm: rbx-3
notifications:
slack:
secure: 6E+GuZId5GcVLgiOKZnE9ZAnJTa+K2Fxqb0KfrECtskviylcPF1OmX36+y7xGUS0P0pGNyWQThe0zCBMpszno/KbvUdIs6jXbOImkFMuo65//4YcivK0rVaYh9uh2S1K3ycaDeUPDf0ulc/AwHnxWGR6yBD2N5idTwt1bsULdy0=
sinatra-2.0.8.1/.yardopts 0000664 0000000 0000000 00000000160 13603175240 0015165 0 ustar 00root root 0000000 0000000 --readme README.md
--title 'Sinatra API Documentation'
--charset utf-8
--markup markdown
'lib/**/*.rb' - '*.md'
sinatra-2.0.8.1/AUTHORS.md 0000664 0000000 0000000 00000007005 13603175240 0014773 0 ustar 00root root 0000000 0000000 Sinatra was designed and developed by Blake Mizerany in California.
### Current Team
* **Konstantin Haase** (maintainer)
* **Zachary Scott**
* **Kashyap Kondamudi**
* **Ashley Williams**
* **Trevor Bramble**
* **Kunpei Sakai**
### Alumni
* **Blake Mizerany** (creator)
* **Ryan Tomayko**
* **Simon Rozet**
* **Katrina Owen**
### Thanks
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](https://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, without whom
Sinatra would not be possible:
* [Ryan Tomayko](http://tomayko.com/) (rtomayko) for constantly fixing
whitespace errors __60d5006__
* [Ezra Zygmuntowicz](http://brainspl.at/) (ezmobius) for initial help and
letting Blake steal some of merbs internal code.
* [Chris Schneider](http://gittr.com) (cschneid) for The Book, the blog,
[irclogger.com](http://irclogger.com/sinatra/), and a bunch of useful
patches.
* [Markus Prinz](http://nuclearsquid.com/) (cypher) for patches over the
years, caring about the README, and hanging in there when times were rough.
* [Erik Kastner](http://metaatem.net/) (kastner) for fixing `MIME_TYPES` under
Rack 0.5.
* [Ben Bleything](http://blog.bleything.net/) (bleything) for caring about HTTP
status codes and doc fixes.
* [Igal Koshevoy](http://twitter.com/igalko) (igal) for root path detection under
Thin/Passenger.
* [Jon Crosby](http://joncrosby.me/) (jcrosby) for coffee breaks, doc fixes, and
just because, man.
* [Karel Minarik](https://github.com/karmi) (karmi) for screaming until the
website came back up.
* [Jeremy Evans](http://code.jeremyevans.net/) (jeremyevans) for unbreaking
optional path params (twice!)
* [The GitHub guys](https://github.com/) for stealing Blake's table.
* [Nickolas Means](http://nmeans.org/) (nmeans) for Sass template support.
* [Victor Hugo Borja](https://github.com/vic) (vic) for splat'n routes specs and
doco.
* [Avdi Grimm](http://avdi.org/) (avdi) for basic RSpec support.
* [Jack Danger Canty](http://jåck.com/) 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](http://getsongbird.com/) for the problems that helped Sinatra's
future become realized.
* [Rick Olson](http://techno-weenie.net/) (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](http://patnakajima.com/) (nakajima) for fixing non-nested
params in nested params Hash's.
* 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](https://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](http://www.sinatra.com/) (chairman of the board) for having so much class he
deserves a web-framework named after him.
sinatra-2.0.8.1/CHANGELOG.md 0000664 0000000 0000000 00000176577 13603175240 0015162 0 ustar 00root root 0000000 0000000 ## 2.0.8.1 / 2020-01-02
* Allow multiple hashes to be passed in `merge` and `merge!` for `Sinatra::IndifferentHash` [#1572](https://github.com/sinatra/sinatra/pull/1572) by Shota Iguchi
## 2.0.8 / 2020-01-01
* Lookup Tilt class for template engine without loading files [#1558](https://github.com/sinatra/sinatra/pull/1558). Fixes [#1172](https://github.com/sinatra/sinatra/issues/1172) by Jordan Owens
* Add request info in NotFound exception [#1566](https://github.com/sinatra/sinatra/pull/1566) by Stefan Sundin
* Add `.yaml` support in `Sinatra::Contrib::ConfigFile` [#1564](https://github.com/sinatra/sinatra/issues/1564). Fixes [#1563](https://github.com/sinatra/sinatra/issues/1563) by Emerson Manabu Araki
* Remove only routing parameters from @params hash [#1569](https://github.com/sinatra/sinatra/pull/1569). Fixes [#1567](https://github.com/sinatra/sinatra/issues/1567) by Jordan Owens, Horacio
* Support `capture` and `content_for` with Hamlit [#1580](https://github.com/sinatra/sinatra/pull/1580) by Takashi Kokubun
* Eliminate warnings of keyword parameter for Ruby 2.7.0 [#1581](https://github.com/sinatra/sinatra/pull/1581) by Osamtimizer
## 2.0.7 / 2019-08-22
* Fix a regression [#1560](https://github.com/sinatra/sinatra/pull/1560) by Kunpei Sakai
## 2.0.6 / 2019-08-21
* Fix an issue setting environment from command line option [#1547](https://github.com/sinatra/sinatra/pull/1547), [#1554](https://github.com/sinatra/sinatra/pull/1554) by Jordan Owens, Kunpei Sakai
* Support pandoc as a new markdown renderer [#1533](https://github.com/sinatra/sinatra/pull/1533) by Vasiliy
* Remove outdated code for tilt 1.x [#1532](https://github.com/sinatra/sinatra/pull/1532) by Vasiliy
* Remove an extra logic for `force_encoding` [#1527](https://github.com/sinatra/sinatra/pull/1527) by Jordan Owens
* Avoid multiple errors even if `params` contains special values [#1526](https://github.com/sinatra/sinatra/pull/1527) by Kunpei Sakai
* Support `bundler/inline` with `require 'sinatra'` integration [#1520](https://github.com/sinatra/sinatra/pull/1520) by Kunpei Sakai
* Avoid `TypeError` when params contain a key without a value on Ruby < 2.4 [#1516](https://github.com/sinatra/sinatra/pull/1516) by Samuel Giddins
* Improve development support and documentation and source code by Olle Jonsson, Basavanagowda Kanur, Yuki MINAMIYA
## 2.0.5 / 2018-12-22
* Avoid FrozenError when params contains frozen value [#1506](https://github.com/sinatra/sinatra/pull/1506) by Kunpei Sakai
* Add support for Erubi [#1494](https://github.com/sinatra/sinatra/pull/1494) by @tkmru
* `IndifferentHash` monkeypatch warning improvements [#1477](https://github.com/sinatra/sinatra/pull/1477) by Mike Pastore
* Improve development support and documentation and source code by Anusree Prakash, Jordan Owens, @ceclinux and @krororo.
### sinatra-contrib
* Add `flush` option to `content_for` [#1225](https://github.com/sinatra/sinatra/pull/1225) by Shota Iguchi
* Drop activesupport dependency from sinatra-contrib [#1448](https://github.com/sinatra/sinatra/pull/1448)
* Update `yield_content` to append default to ERB template buffer [#1500](https://github.com/sinatra/sinatra/pull/1500) by Jordan Owens
### rack-protection
* Don't track the Accept-Language header by default [#1504](https://github.com/sinatra/sinatra/pull/1504) by Artem Chistyakov
## 2.0.4 / 2018-09-15
* Don't blow up when passing frozen string to `send_file` disposition [#1137](https://github.com/sinatra/sinatra/pull/1137) by Andrew Selder
* Fix ubygems LoadError [#1436](https://github.com/sinatra/sinatra/pull/1436) by Pavel Rosický
* Unescape regex captures [#1446](https://github.com/sinatra/sinatra/pull/1446) by Jordan Owens
* Slight performance improvements for IndifferentHash [#1427](https://github.com/sinatra/sinatra/pull/1427) by Mike Pastore
* Improve development support and documentation and source code by Will Yang, Jake Craige, Grey Baker and Guilherme Goettems Schneider
## 2.0.3 / 2018-06-09
* Fix the backports gem regression [#1442](https://github.com/sinatra/sinatra/issues/1442) by Marc-André Lafortune
## 2.0.2 / 2018-06-05
* Escape invalid query parameters [#1432](https://github.com/sinatra/sinatra/issues/1432) by Kunpei Sakai
* The patch fixes [CVE-2018-11627](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-11627).
* Fix undefined method error for `Sinatra::RequiredParams` with hash key [#1431](https://github.com/sinatra/sinatra/issues/1431) by Arpit Chauhan
* Add xml content-types to valid html_types for Rack::Protection [#1413](https://github.com/sinatra/sinatra/issues/1413) by Reenan Arbitrario
* Encode route parameters using :default_encoding setting [#1412](https://github.com/sinatra/sinatra/issues/1412) by Brian m. Carlson
* Fix unpredictable behaviour from Sinatra::ConfigFile [#1244](https://github.com/sinatra/sinatra/issues/1244) by John Hope
* Add Sinatra::IndifferentHash#slice [#1405](https://github.com/sinatra/sinatra/issues/1405) by Shota Iguchi
* Remove status code 205 from drop body response [#1398](https://github.com/sinatra/sinatra/issues/1398) by Shota Iguchi
* Ignore empty captures from params [#1390](https://github.com/sinatra/sinatra/issues/1390) by Shota Iguchi
* Improve development support and documentation and source code by Zp Yuan, Andreas Finger, Olle Jonsson, Shota Iguchi, Nikita Bulai and Joshua O'Brien
## 2.0.1 / 2018-02-17
* Repair nested namespaces, by avoiding prefix duplication [#1322](https://github.com/sinatra/sinatra/issues/1322). Fixes [#1310](https://github.com/sinatra/sinatra/issues/1310) by Kunpei Sakai
* Add pattern matches to values for Mustermann::Concat [#1333](https://github.com/sinatra/sinatra/issues/1333). Fixes [#1332](https://github.com/sinatra/sinatra/issues/1332) by Dawa Ometto
* Ship the VERSION file with the gem, to allow local unpacking [#1338](https://github.com/sinatra/sinatra/issues/1338) by Olle Jonsson
* Fix issue with custom error handler on bad request [#1351](https://github.com/sinatra/sinatra/issues/1351). Fixes [#1350](https://github.com/sinatra/sinatra/issues/1350) by Jordan Owens
* Override Rack::ShowExceptions#pretty to set custom template [#1377](https://github.com/sinatra/sinatra/issues/1377). Fixes [#1376](https://github.com/sinatra/sinatra/issues/1376) by Jordan Owens
* Enhanced path validation in Windows [#1379](https://github.com/sinatra/sinatra/issues/1379) by Orange Tsai from DEVCORE
* The patch fixes [CVE-2018-7212](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-7212)
* Improve development support and documentation by Faheel Ahmad, Shota Iguchi, Olle Jonsson, Manabu Niseki, John Hope, Horacio, Ice-Storm, GraniteRock, Raman Skaskevich, Carlos Azuaje, 284km, Dan Rice and Zachary Scott
## 2.0.0 / 2017-04-10
* Use Mustermann for patterns [#1086](https://github.com/sinatra/sinatra/issues/1086) by Konstantin Haase
* Server now provides `-q` flag for quiet mode, which disables start/stop messages [#1153](https://github.com/sinatra/sinatra/issues/1153) by Vasiliy.
* Session middleware can now be specified with `:session_store` setting [#1161](https://github.com/sinatra/sinatra/issues/1161) by Jordan Owens.
* `APP_ENV` is now preferred and recommended over `RACK_ENV` for setting environment [#984](https://github.com/sinatra/sinatra/issues/984) by Damien Mathieu.
* Add Reel support [#793](https://github.com/sinatra/sinatra/issues/793) by Patricio Mac Adden.
* Make route params available during error handling [#895](https://github.com/sinatra/sinatra/issues/895) by Jeremy Evans.
* Unify `not_found` and `error` 404 behavior [#896](https://github.com/sinatra/sinatra/issues/896) by Jeremy Evans.
* Enable Ruby 2.3 `frozen_string_literal` feature [#1076](https://github.com/sinatra/sinatra/issues/1076) by Vladimir Kochnev.
* Add Sinatra::ShowExceptions::TEMPLATE and patched Rack::ShowExceptions to prefer Sinatra template by Zachary Scott.
* Sinatra::Runner is used internally for integration tests [#840](https://github.com/sinatra/sinatra/issues/840) by Nick Sutterer.
* Fix case-sensitivity issue in `uri` method [#889](https://github.com/sinatra/sinatra/issues/889) by rennex.
* Use `Rack::Utils.status_code` to allow `status` helper to use symbol as well as numeric codes [#968](https://github.com/sinatra/sinatra/issues/968) by Tobias H. Michaelsen.
* Improved error handling for invalid params through Rack [#1070](https://github.com/sinatra/sinatra/issues/1070) by Jordan Owens.
* Ensure template is cached only once [#1021](https://github.com/sinatra/sinatra/issues/1021) by Patrik Rak.
* Rack middleware is initialized at server runtime rather than after receiving first request [#1205](https://github.com/sinatra/sinatra/issues/1205) by Itamar Turner-Trauring.
* Improve Session Secret documentation to encourage better security practices [#1218](https://github.com/sinatra/sinatra/issues/1218) by Glenn Rempe
* Exposed global and per-route options for Mustermann route parsing [#1233](https://github.com/sinatra/sinatra/issues/1233) by Mike Pastore
* Use same `session_secret` for classic and modular apps in development [#1245](https://github.com/sinatra/sinatra/issues/1245) by Marcus Stollsteimer
* Make authenticity token length a fixed value of 32 [#1181](https://github.com/sinatra/sinatra/issues/1181) by Jordan Owens
* Modernize Rack::Protection::ContentSecurityPolicy with CSP Level 2 and 3 Directives [#1202](https://github.com/sinatra/sinatra/issues/1202) by Glenn Rempe
* Adds preload option to Rack:Protection:StrictTransport [#1209](https://github.com/sinatra/sinatra/issues/1209) by Ed Robinson
* Improve BadRequest logic. Raise and handle exceptions if status is 400 [#1212](https://github.com/sinatra/sinatra/issues/1212) by Mike Pastore
* Make Rack::Test a development dependency [#1232](https://github.com/sinatra/sinatra/issues/1232) by Mike Pastore
* Capture exception messages of raised NotFound and BadRequest [#1210](https://github.com/sinatra/sinatra/issues/1210) by Mike Pastore
* Add explicit set method to contrib/cookies to override cookie settings [#1240](https://github.com/sinatra/sinatra/issues/1240) by Andrew Allen
* Avoid executing filters even if prefix matches with other namespace [#1253](https://github.com/sinatra/sinatra/issues/1253) by namusyaka
* Make `#has_key?` also indifferent in access, can accept String or Symbol [#1262](https://github.com/sinatra/sinatra/issues/1262) by Stephen Paul Weber
* Add `allow_if` option to bypass json csrf protection [#1265](https://github.com/sinatra/sinatra/issues/1265) by Jordan Owens
* rack-protection: Bundle StrictTransport, CookieTossing, and CSP [#1267](https://github.com/sinatra/sinatra/issues/1267) by Mike Pastore
* Add `:strict_paths` option for managing trailing slashes [#1273](https://github.com/sinatra/sinatra/issues/1273) by namusyaka
* Add full IndifferentHash implementation to params [#1279](https://github.com/sinatra/sinatra/issues/1279) by Mike Pastore
## 1.4.8 / 2017-01-30
* Fix the deprecation warning from Ruby about Fixnum. [#1235](https://github.com/sinatra/sinatra/issues/1235) by Akira Matsuda
## 1.4.7 / 2016-01-24
* Add Ashley Williams, Trevor Bramble, and Kashyap Kondamudi to team Sinatra.
* Correctly handle encoded colons in routes. (Jeremy Evans)
* Rename CHANGES to CHANGELOG.md and update Rakefile. [#1043](https://github.com/sinatra/sinatra/issues/1043) (Eliza Sorensen)
* Improve documentation. [#941](https://github.com/sinatra/sinatra/issues/941), [#1069](https://github.com/sinatra/sinatra/issues/1069), [#1075](https://github.com/sinatra/sinatra/issues/1075), [#1025](https://github.com/sinatra/sinatra/issues/1025), [#1052](https://github.com/sinatra/sinatra/issues/1052) (Many great folks)
* Introduce `Sinatra::Ext` to workaround Rack 1.6 bug to fix Ruby 1.8.7
support. [#1080](https://github.com/sinatra/sinatra/issues/1080) (Zachary Scott)
* Add CONTRIBUTING guide. [#987](https://github.com/sinatra/sinatra/issues/987) (Katrina Owen)
## 1.4.6 / 2015-03-23
* Improve tests and documentation. (Darío Hereñú, Seiichi Yonezawa, kyoendo,
John Voloski, Ferenc-, Renaud Martinet, Christian Haase, marocchino,
huoxito, Damir Svrtan, Amaury Medeiros, Jeremy Evans, Kashyap, shenqihui,
Ausmarton Fernandes, kami, Vipul A M, Lei Wu, 7stud, Taylor Shuler,
namusyaka, burningTyger, Cornelius Bock, detomastah, hakeda, John Hope,
Ruben Gonzalez, Andrey Deryabin, attilaolah, Anton Davydov, Nikita Penzin,
Dyego Costa)
* Remove duplicate require of sinatra/base. (Alexey Muranov)
* Escape HTML in 404 error page. (Andy Brody)
* Refactor to method call in `Stream#close` and `#callback`. (Damir Svrtan)
* Depend on latest version of Slim. (Damir Svrtan)
* Fix compatibility with Tilt version 2. (Yegor Timoschenko)
* Fix compatibility issue with Rack `pretty` method from ShowExceptions.
(Kashyap)
* Show date in local time in exception messages. (tayler1)
* Fix logo on error pages when using Ruby 1.8. (Jeremy Evans)
* Upgrade test suite to Minitest version 5 and fix Ruby 2.2 compatibility.
(Vipul A M)
## 1.4.5 / 2014-04-08
* Improve tests and documentation. (Seiichi Yonezawa, Mike Gehard, Andrew
Deitrick, Matthew Nicholas Bradley, GoGo tanaka, Carlos Lazo, Shim Tw,
kyoendo, Roman Kuznietsov, Stanislav Chistenko, Ryunosuke SATO, Ben Lewis,
wuleicanada, Patricio Mac Adden, Thais Camilo)
* Fix Ruby warnings. (Vipul A M, Piotr Szotkowski)
* Fix template cache memory leak. (Scott Holden)
* Work around UTF-8 bug in JRuby. (namusyaka)
* Don't set charset for JSON mime-type (Sebastian Borrazas)
* Fix bug in request.accept? that might trigger a NoMethodError. (sbonami)
## 1.4.4 / 2013-10-21
* Allow setting layout to false specifically for a single rendering engine.
(Matt Wildig)
* Allow using wildcard in argument passed to `request.accept?`. (wilkie)
* Treat missing Accept header like wild card. (Patricio Mac Adden)
* Improve tests and documentation. (Darío Javier Cravero, Armen P., michelc,
Patricio Mac Adden, Matt Wildig, Vipul A M, utenmiki, George Timoschenko,
Diogo Scudelletti)
* Fix Ruby warnings. (Vipul A M, Patricio Mac Adden)
* Improve self-hosted server started by `run!` method or in classic mode.
(Tobias Bühlmann)
* Reduce objects allocated per request. (Vipul A M)
* Drop unused, undocumented options hash from Sinatra.new. (George Timoschenko)
* Keep Content-Length header when response is a `Rack::File` or when streaming.
(Patricio Mac Adden, George Timoschenko)
* Use reel if it's the only server available besides webrick. (Tobias Bühlmann)
* Add `disable :traps` so setting up signal traps for self hosted server can be
skipped. (George Timoschenko)
* The `status` option passed to `send_file` may now be a string. (George
Timoschenko)
* Reduce file size of dev mode images for 404 and 500 pages. (Francis Go)
## 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
happened 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](https://github.com/sinatra/sinatra/issues/239) and GH[#256](https://github.com/sinatra/sinatra/issues/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_exceptions` 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-2.0.8.1/CONTRIBUTING.md 0000664 0000000 0000000 00000011600 13603175240 0015551 0 ustar 00root root 0000000 0000000 # Contribute
Want to show Sinatra some love? Help out by contributing!
## Found a bug?
Log it in our [issue tracker][ghi] or send a note to the [mailing list][ml].
Be sure to include all relevant information, like the versions of Sinatra and
Ruby you're using. A [gist](http://gist.github.com/) of the code that caused
the issue as well as any error messages are also very helpful.
## Need help?
The [Sinatra mailing list][ml] has over 900 subscribers, many of which are happy
to help out newbies or talk about potential feature additions. You can also
drop by the [#sinatra](irc://chat.freenode.net/#sinatra) channel on
[irc.freenode.net](http://freenode.net).
## Have a patch?
Bugs and feature requests that include patches are much more likely to
get attention. Here are some guidelines that will help ensure your patch
can be applied as quickly as possible:
1. **Use [Git](http://git-scm.com) and [GitHub](http://github.com):**
The easiest way to get setup is to fork the
[sinatra/sinatra repo](http://github.com/sinatra/sinatra/).
Or, the [sinatra.github.com repo](http://github.com/sinatra/sinatra.github.com/),
if the patch is doc related.
2. **Write unit tests:** If you add or modify functionality, it must
include unit tests. If you don't write tests, we have to, and this
can hold up acceptance of the patch.
3. **Mind the `README`:** If the patch adds or modifies a major feature,
modify the `README.md` file to reflect that. Again, if you don't
update the `README`, we have to, and this holds up acceptance.
4. **Push it:** Once you're ready, push your changes to a topic branch
and add a note to the ticket with the URL to your branch. Or, say
something like, "you can find the patch on johndoe/foobranch". We also
gladly accept GitHub [pull requests](http://help.github.com/pull-requests/).
__NOTE:__ _We will take whatever we can get._ If you prefer to attach diffs in
emails to the mailing list, that's fine; but do know that _someone_ will need
to take the diff through the process described above and this can hold things
up considerably.
## Want to write docs?
The process for contributing to Sinatra's website, documentation or the book
is the same as contributing code. We use Git for versions control and GitHub to
track patch requests.
* [The sinatra.github.com repo](http://github.com/sinatra/sinatra.github.com/)
is where the website sources are managed. There are almost always people in
`#sinatra` that are happy to discuss, apply, and publish website patches.
* [The Book](http://sinatra-org-book.herokuapp.com/) has its own [Git
repository](http://github.com/sinatra/sinatra-book/) and build process but is
managed the same as the website and project codebase.
* [Sinatra Recipes](http://recipes.sinatrarb.com/) is a community
project where anyone is free to contribute ideas, recipes and tutorials. Which
also has its own [Git repository](http://github.com/sinatra/sinatra-recipes).
* [The Introduction](http://www.sinatrarb.com/intro.html) is generated from
Sinatra's [README file](http://github.com/sinatra/sinatra/blob/master/README.md).
* If you want to help translating the documentation, the README is already
available in
[Japanese](http://github.com/sinatra/sinatra/blob/master/README.ja.md),
[German](http://github.com/sinatra/sinatra/blob/master/README.de.md),
[Chinese](https://github.com/sinatra/sinatra/blob/master/README.zh.md),
[Russian](https://github.com/sinatra/sinatra/blob/master/README.ru.md),
[European](https://github.com/sinatra/sinatra/blob/master/README.pt-pt.md) and
[Brazilian](https://github.com/sinatra/sinatra/blob/master/README.pt-br.md)
Portuguese,
[French](https://github.com/sinatra/sinatra/blob/master/README.fr.md),
[Spanish](https://github.com/sinatra/sinatra/blob/master/README.es.md),
[Korean](https://github.com/sinatra/sinatra/blob/master/README.ko.md), and
[Hungarian](https://github.com/sinatra/sinatra/blob/master/README.hu.md).
The translations tend to fall behind the English version. Translations into
other languages would also be appreciated.
## Looking for something to do?
If you'd like to help out but aren't sure how, pick something that looks
interesting from the [issues][ghi] list and hack on. Make sure to leave a
comment on the ticket noting that you're investigating (a simple "Taking…" is
fine).
[ghi]: http://github.com/sinatra/sinatra/issues
[ml]: http://groups.google.com/group/sinatrarb/topics "Sinatra Mailing List"
* ["Help Wanted"](https://github.com/sinatra/sinatra/labels/help%20wanted): Anyone willing to pitch in is open to contribute to this ticket as they see fit (will try to add context / summarize or ask for requirements)
* ["Good First Issue"](https://github.com/sinatra/sinatra/labels/good%20first%20issue): Potential first time contributors should start here
* ["Wishlist"](https://github.com/sinatra/sinatra/labels/Wishlist): All the things I wish we had but have no time for
sinatra-2.0.8.1/Gemfile 0000664 0000000 0000000 00000003272 13603175240 0014621 0 ustar 00root root 0000000 0000000 # 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', git: 'https://github.com/rack/rack.git'
gem 'rack-test', '>= 0.6.2'
gem "minitest", "~> 5.0"
gem 'yard'
gem "rack-protection", path: "rack-protection"
gem "sinatra-contrib", path: "sinatra-contrib"
gem "twitter-text", "1.14.7"
if RUBY_ENGINE == 'jruby'
gem 'nokogiri', '!= 1.5.0'
gem 'trinidad'
end
if RUBY_ENGINE == 'jruby' || RUBY_ENGINE == 'ruby'
gem "activesupport", "~> 5.1.6"
end
if RUBY_ENGINE == "ruby"
gem 'less', '~> 2.0'
gem 'therubyracer'
gem 'redcarpet'
gem 'wlang', '>= 2.0.1'
gem 'bluecloth'
gem 'rdiscount'
gem 'RedCloth'
gem 'puma'
gem 'yajl-ruby'
gem 'nokogiri'
gem 'thin'
gem 'slim', '~> 2.0'
gem 'coffee-script', '>= 2.0'
gem 'rdoc'
gem 'kramdown'
gem 'maruku'
gem 'creole'
gem 'wikicloth'
gem 'markaby'
gem 'radius'
gem 'asciidoctor'
gem 'liquid'
gem 'stylus'
gem 'rabl'
gem 'builder'
gem 'erubi'
gem 'erubis'
gem 'haml', '>= 3.0'
gem 'sass'
gem 'reel-rack'
gem 'celluloid', '~> 0.16.0'
gem 'commonmarker', '~> 0.20.0'
gem 'pandoc-ruby', '~> 2.0.2'
gem 'simplecov', require: false
end
if RUBY_ENGINE == "rbx"
gem 'json'
gem 'rubysl'
gem 'rubysl-test-unit'
gem 'erubi'
end
platforms :jruby do
gem 'json'
end
sinatra-2.0.8.1/LICENSE 0000664 0000000 0000000 00000002224 13603175240 0014327 0 ustar 00root root 0000000 0000000 The MIT License (MIT)
Copyright (c) 2007, 2008, 2009 Blake Mizerany
Copyright (c) 2010-2017 Konstantin Haase
Copyright (c) 2015-2017 Zachary Scott
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-2.0.8.1/MAINTENANCE.md 0000664 0000000 0000000 00000002660 13603175240 0015372 0 ustar 00root root 0000000 0000000 # Sinatra maintenance
## Versions
### Unstable release
The next major version of Sinatra will be released from the master branch.
* Current proposed major release: 2.0.0
### Stable release
The current stable version of Sinatra is 1.4 series, and released from the stable branch.
## Issues
### New features
New features will only be added to the master branch and will not be made available in point releases.
### Bug fixes
Only the latest release series will receive bug fixes. When enough bugs are fixed and its deemed worthy to release a new gem, this is the branch it happens from.
* Current release series: 1.4.x
### Security issues
The current release series will receive patches and new versions in case of a security issue.
* Current release series: 1.4.x
### Severe security issues
For severe security issues we will provide new versions as above, and also the last major release series will receive patches and new versions. The classification of the security issue is judged by the core team.
* Current release series: 1.4.x
* Next most recent release series: 1.3.x
### Unsupported Release Series
When a release series is no longer supported, it’s your own responsibility to deal with bugs and security issues. We may provide back-ports of the fixes and publish them to git, however there will be no new versions released. If you are not comfortable maintaining your own versions, you should upgrade to a supported version.
sinatra-2.0.8.1/README.de.md 0000664 0000000 0000000 00000246643 13603175240 0015207 0 ustar 00root root 0000000 0000000 # Sinatra
[](http://travis-ci.org/sinatra/sinatra)
*Wichtig: Dieses Dokument ist eine Übersetzung aus dem Englischen und unter
Umständen nicht auf dem aktuellen Stand (aktuell Sinatra 2.0
Vorabausgabe).*
Sinatra ist eine
[DSL](https://de.wikipedia.org/wiki/Domänenspezifische_Sprache), die das
schnelle Erstellen von Webanwendungen in Ruby mit minimalem Aufwand
ermöglicht:
```ruby
# myapp.rb
require 'sinatra'
get '/' do
'Hallo Welt!'
end
```
Sinatra-Gem installieren:
```shell
gem install sinatra
```
und im gleichen Verzeichnis ausführen:
```shell
ruby myapp.rb
```
Die Seite kann nun unter [http://localhost:4567](http://localhost:4567)
aufgerufen werden.
Es wird empfohlen `gem installl thin` auszuführen, Sinatra wird dann
diesen Server verwenden.
## Inhalt
- [Sinatra](#sinatra)
* [Inhalt](#inhalt)
* [Routen](#routen)
+ [Bedingungen](#bedingungen)
+ [Rückgabewerte](#rückgabewerte)
+ [Eigene Routen-Muster](#eigene-routen-muster)
* [Statische Dateien](#statische-dateien)
* [Views/Templates](#views-templates)
+ [Direkte Templates](#direkte-templates)
+ [Verfügbare Templatesprachen](#verfügbare-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)
- [AsciiDoc Templates](#asciidoc-templates)
- [Radius Templates](#radius-templates)
- [Markaby Templates](#markaby-templates)
- [RABL Templates](#rabl-templates)
- [Slim Templates](#slim-templates)
- [Creole Templates](#creole-templates)
- [MediaWiki Templates](#mediawiki-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-hinzuf-gen)
+ [Eigene Methoden zum Aufsuchen von Templates verwenden](#eigene-methoden-zum-aufsuchen-von-templates-verwenden)
* [Filter](#filter)
* [Helfer](#helfer)
+ [Sessions verwenden](#sessions-verwenden)
- [Sitzungseinstellungen](#sitzungseinstellungen)
- [Eigene Sitzungs-Middleware auswählen](#eigene-sitzungs-middleware-ausw-hlen)
+ [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](#anhänge)
+ [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](#m-gliche-einstellungen)
* [Umgebungen](#umgebungen)
* [Fehlerbehandlung](#fehlerbehandlung)
+ [Nicht gefunden](#nicht-gefunden)
+ [Fehler](#fehler)
* [Rack-Middleware](#rack-middleware)
* [Testen](#testen)
* [Sinatra::Base - Middleware, Bibliotheken und modulare Anwendungen](#sinatra--base---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)
+ [Multi-threading](#multi-threading)
* [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.
Routen mit angehängtem Schrägstrich unterscheiden sich von Routen ohne:
```ruby
get '/foo' do
# wird nicht bei "GET /foo/" aufgerufen
end
```
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 /\/hallo\/([\w]+)/ do
"Hallo, #{params['captures'].first}!"
end
```
Und auch hier können Block-Parameter genutzt werden:
```ruby
get %r{/hallo/([\w]+)} do |c|
# erkennt "GET /hallo/frank" oder "GET /sag/hallo/frank" usw.
"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
```
Routen können auch den query-Parameter verwenden:
```ruby
get '/posts' do
# passt zu "GET /posts?title=foo&author=bar"
title = params['title']
author = params['author']
# verwendet title- und author-Variablen. Der query-Parameter ist für
# die /post-Route optional
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.
Die Mustermann-Optionen können für eine gegebene Route angepasst werden,
indem man der Route den `:mustermann_opts`-Hash mitgibt:
```ruby
get '\A/posts\z', :mustermann_opts => { :type => :regexp, :check_anchors => false } do
# Passt genau auf /posts mit explizitem Anchoring
"Wenn Dein Anchor-Muster passt, darfst Du klatschen!"
end
```
Das sieht zwar genauso aus wie eine Bedingung, ist es aber nicht. Diese
Option wird mit dem globalen `:mustermann_opts`-Hash zusammengeführt
(siehe weiter unten).
### 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 verfügbare 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
```
`provides` durchsucht den Accept-Header der Anfrage
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 (Integer), Headers (Hash),
Response-Body (antwortet auf #each)]`.
* Ein Array mit zwei Elementen: `[Status (Integer), 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 Integer, 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'
```
Wird einer Rendering-Methode ein String übergeben, wird dieser direkt
gerendert.
### Direkte Templates
```ruby
get '/' do
haml '%div.title Hallo Welt'
end
```
Hier wird der String direkt gerendert.
Optional kann `:path` und `:line` für einen genaueren Backtrace
übergeben werden, wenn mit dem vorgegebenen String ein Pfad im
Dateisystem oder eine Zeilennummer verbunden ist:
```ruby
get '/' do
haml '%div.title Hallo Welt', :path => 'examples/datei.haml', :line => 3
end
```
### 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
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.
#### AsciiDoc Templates
Abhängigkeit |
Asciidoctor |
Dateierweiterungen |
.asciidoc, .adoc und .ad |
Beispiel |
asciidoc :README, :layout_engine => :erb |
Da man aus dem AsciiDoc-Template heraus keine Ruby-Methoden aufrufen kann
(ausgenommen `yield`), wird man üblicherweise locals verwenden wollen, mit
denen man Variablen weitergibt.
#### 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.
#### MediaWiki Templates
Abhängigkeit |
WikiCloth |
Dateierweiterungen |
.mediawiki und .mw |
Beispiel |
mediawiki :wiki, :layout_engine => :erb |
Da man aus dem Mediawiki-Template heraus keine Ruby-Methoden aufrufen
und auch keine locals verwenden kann, wird man Mediawiki üblicherweise
in Kombination mit anderen Renderern verwenden wollen:
```ruby
erb :overview, :locals => { :text => mediawiki(:introduction) }
```
Beachte: Man kann die `mediawiki`-Methode auch aus anderen Templates
heraus aufrufen:
```ruby
%h1 Grüße von Haml!
%p= mediawiki(:greetings)
```
Da man Ruby nicht von MediaWiki heraus aufrufen kann, können auch
Layouts nicht in MediaWiki 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
#### Stylus Templates
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:
```javascript
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
weiteren 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 individuell nach Route
verhindert werden, oder generell für ein Template, z.B. Haml via:
`set :haml, :layout => false`:
```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.
### Eigene Methoden zum Aufsuchen von Templates verwenden
Um einen eigenen Mechanismus zum Aufsuchen von Templates zu
implementieren, muss `#find_template` definiert werden:
```ruby
configure do
set :views [ './views/a', './views/b' ]
end
def find_template(views, name, engine, &block)
Array(views).each do |v|
super(v, name, engine, &block)
end
end
```
## 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
```
Achtung: Wenn statt der body-Methode ein einfacher String verwendet
wird, ist der Response-body im after-Filter noch nicht verfügbar, da
er erst nach dem Durchlaufen des after-Filters erzeugt wird.
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
```
Ebenso können Helfer auch in einem eigenen Modul definiert werden:
```ruby
module FooUtils
def foo(name) "#{name}foo" end
end
module BarUtils
def bar(name) "#{name}bar" end
end
helpers FooUtils, BarUtils
```
Das Ergebnis ist das gleiche, wie beim Einbinden in die
Anwendungs-Klasse.
### 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
```
Um die Sicherheit zu erhöhen, werden Daten mit einem geheimen
Sitzungsschlüssel unter Verwendung von `HMAC-SHA1` in einem Cookie signiert.
Der Sitzungsschlüssel sollte optimalerweise ein kryptografisch zufällig
erzeugter Wert mit angemessener Länge sein, für den `HMAC-SHA1` größer
oder gleich 65 Bytes ist (256 Bits, 64 Hex-Zeichen). Es wird empfohlen,
keinen Schlüssel zu verwenden, dessen Zufälligkeit weniger als 32 Bytes
entspricht (also 256 Bits, 64 Hex-Zeichen). Es ist deshalb **wirklich
wichtig**, dass nicht einfach irgendetwas als Schlüssel verwendet wird,
sondern ein sicherer Zufallsgenerator die Zeichenkette erstellt. Menschen sind
nicht besonders gut, zufällige Zeichenfolgen zu erstellen.
Sinatra generiert automatisch einen zufälligen 32 Byte langen zufälligen
Schlüssel. Da jedoch bei jedem Neustart der Schlüssel ebenfalls neu generiert
wird, ist es sinnvoll einen eigenen Schlüssel festzulegen, damit er über alle
Anwendungsinstanzen hinweg geteilt werden kann.
Aus praktikablen und Sicherheitsgründen wird
[empfohlen](https://12factor.net/config), dass ein sicherer Zufallswert
erzeugt und in einer Umgebungsvariable abgelgegt wird, damit alle
Anwendungsinstanzen darauf zugreifen können. Dieser Sitzungsschlüssel
sollte in regelmäßigen Abständen erneuert werden. Zum Erzeugen von 64
Byte starken Schlüsseln sind hier ein paar Beispiele vorgestellt:
**Sitzungsschlüssel erzeugen**
```text
$ ruby -e "require 'securerandom'; puts SecureRandom.hex(64)"
99ae8afi...usw...ec0f262ac
```
**Sitzungsschlüssel erzeugen (Bonuspunkte)**
Um den systemweiten Zufallszahlengenerator zu verwenden, kann das
[sysrandom gem](https://github.com/cryptosphere/sysrandom) installiert
werden, anstelle von Zufallszahlen aus dem Userspace, auf die MRI zur
Zeit standardmäßig zugreift:
```text
$ gem install sysrandom
Building native extensions. This could take a while...
Successfully installed sysrandom-1.x
1 gem installed
$ ruby -e "require 'sysrandom/securerandom'; puts SecureRandom.hex(64)"
99ae8af...snip...ec0f262ac
```
**Sitzungsschlüssel-Umgebungsvariable**
Wird eine `SESSION_SECRET`-Umgebungsvariable persistent gesetzt, kann
Sinatra darauf zugreifen. Da die folgende Methode von System zu System
variieren kann, ist dies als Beispiel zu verstehen:
```bash
$ echo "export SESSION_SECRET=99ae8af...etc...ec0f262ac" >> ~/.bashrc
```
**Anwendungseinstellung für Sitzungsschlüssel**
Die Anwendung sollte unabhängig von der `SESSION_SECRET`-Umgebungsvariable
auf einen sicheren zufälligen Schlüssel zurückgreifen.
Auch hier sollte das
[sysrandom gem](https://github.com/cryptosphere/sysrandom) verwendet
werden:
```ruby
require 'securerandom'
# -or- require 'sysrandom/securerandom'
set :session_secret, ENV.fetch('SESSION_SECRET') { SecureRandom.hex(64) }
```
#### Sitzungseinstellungen
Im Options-Hash können weitere Einstellungen abgelegt werden:
```ruby
set :sessions, :domain => 'foo.com'
```
Um Sitzungsdaten über mehrere Anwendungen und Subdomains hinweg zu
teilen, muss die Domain mit einem `*.*` vor der Domain ausgestattet
werden:
```ruby
set :sessions, :domain => '.foo.com'
```
#### Eigene Sitzungs-Middleware auswählen
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. Es kann daher eine beliebige
Rack-Session Middleware verwendet werden. Folgende Methoden stehen zur
Verfügung:
```ruby
enable :sessions
set :session_store, Rack::Session::Pool
```
Oder Sitzungen werden mit einem Options-Hash ausgestattet:
```ruby
set :sessions, :expire_after => 2592000
set :session_store, Rack::Session::Pool
```
Eine weitere Methode ist der Verzicht auf `enable :session` und
stattdessen die Verwendung einer beliebigen anderen Middleware.
Dabei ist jedoch zu beachten, dass der reguläre sitzungsbasierte
Sicherungsmechanismus **nicht automatisch aktiviert wird**.
Die dazu benötigte Rack-Middleware muss explizit eingebunden werden:
```ruby
use Rack::Session::Pool, :expire_after => 2592000
use Rack::Protection::RemoteToken
use Rack::Protection::SessionHijacking
```
Mehr dazu unter [Einstellung des Angiffsschutzes](https://github.com/sinatra/sinatra/blob/master/README.de.md#einstellung-des-angriffsschutzes).
### 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.
Weitere Informationen zu `call` finden sich in den Rack-Spezifikationen.
### 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](https://w3c.github.io/eventsource/), die als Basis für
[WebSockets](https://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 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) do |out|
connections << out
# tote Verbindungen entfernen
connections.reject!(&:closed?)
end
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
```
Es ist ebenfalls möglich, dass der Client die Verbindung schließt, während in
den Socket geschrieben wird. Deshalb ist es sinnvoll, vor einem
Schreibvorgang `out.closed?` zu prüfen.
### 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://www.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](https://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
Um den Inhalt einer Datei als Response zurückzugeben, 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.
- Status
- Zu versendender Status-Code. Nützlich, wenn eine statische Datei
als Fehlerseite zurückgegeben werden soll. Wenn der Rack-Handler es
unterstützt, dann können auch andere Methoden außer Streaming vom
Ruby-Prozess verwendet werden. Wird diese Helfermethode verwendet,
dann wird Sinatra sich automatisch um die Range-Anfrage kümmern.
### Das Request-Objekt
Auf das `request`-Objekt der eigehenden Anfrage kann vom Anfrage-Scope aus
zugegriffen werden (d.h. Filter, Routen, Fehlerbehandlung):
```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, 2016')
"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 zusammengebastelt 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 (`APP_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/sinatra/sinatra/tree/master/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]
```
Standardmäßig setzt Sinatra einen sitzungbasierten Schutz nur dann ein,
wenn `:sessions` aktiviert wurde (siehe oben). Manchmal kann es aber
auch sein, dass Sitzungen außerhalb von Sinatra eingerichtet werden,
z.B. über eine config.ru oder einer zusätzlichen
`Rack::Builder`-Instance. In diesen Situationen kann eine
sitzungbasierte Sicherung eingesetzt werden mit Hilfe der
`:session`-Option:
```ruby
set :protection, session => true
```
#### 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_charset
-
Mime-Types werden hier automatisch der Helfer-Methode
content_type zugeordnet. Es empfielt sich, Werte
hinzuzufügen statt sie zu überschreiben: settings.add_charset
<< "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 ENV['APP_ENV'] 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.
- mustermann_opts
-
Ein Hash mit Standardeinstellungen, der an Mustermann.new beim
Kompilieren der Routen übergeben wird.
- 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.
- quiet
-
Deaktiviert Logs, die beim Starten und Beenden von Sinatra
ausgegeben werden. false ist die Standardeinstellung.
- public_dir
- Alias für public_folder, s.o.
- reload_templates
-
Legt fest, ob Templates für jede Anfrage neu generiert werden. 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::Cookie aktivieren. Für weitere Infos bitte
in der Sektion ‘Sessions verwenden’ nachschauen.
- session_store
-
Die verwendete Rack Sitzungs-Middleware. Verweist standardmäßig
auf Rack::Session::Cookie. 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.
- traps
- Legt fest, wie Sinatra mit System-Signalen umgehen soll.
- 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 `APP_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
`APP_ENV`-Umgebungsvariable setzen:
```shell
APP_ENV=production ruby my_app.rb
```
In der Anwendung kann man die die Methoden `development?`, `test?` und
`production?` verwenden, um die aktuelle Umgebung zu erfahren:
```ruby
get '/' do
if settings.development?
"development!"
else
"nicht development!"
end
end
```
## 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. In der
`development`-Umgebung wird es nur dann funktionieren, wenn die
`:show_exceptions`-Option auf `:after_handler` eingestellt wurde:
```ruby
set :show_exceptions, :after_handler
```
Die Exception kann über die `sinatra.error`-Rack-Variable angesprochen werden:
```ruby
error do
'Entschuldige, es gab einen hässlichen Fehler - ' + env['sinatra.error'].message
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.github.io/) auf, 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://www.rubydoc.info/github/rack/rack/master/Rack/Builder)-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),
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://www.rubydoc.info/github/brynary/rack-test/master/frames)
wird empfohlen:
```ruby
require 'my_sinatra_app'
require 'minitest/autorun'
require 'rack/test'
class MyAppTest < Minitest::Test
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_user_agent
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 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'` 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://www.sinatrarb.com/configuration.html) für
Details über mögliche Optionen.
Damit eine App sich ähnlich wie eine klassische App verhält, kann man
auch eine Subclass von `Sinatra::Application` erstellen:
```ruby
require 'sinatra/base'
class MyApp < Sinatra::Application
get '/' do
'Hello world!'
end
end
```
### 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 |
Modular |
app_file |
Sinatra ladende Datei |
Sinatra::Base subklassierende Datei |
Sinatra::Application subklassierende Datei |
run |
$0 == app_file |
false |
false |
logging |
true |
false |
true |
method_override |
true |
false |
true |
inline_templates |
true |
false |
true |
static |
true |
File.exist?(public_folder) |
true |
### 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/Hanami/Roda/...):
```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:
* Innerhalb 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/Views
### 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] [-q] [-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)
-q # den lautlosen Server-Modus einschalten (Standard ist aus)
-x # Mutex-Lock einschalten (Standard ist aus)
```
### Multi-threading
_Paraphrasiert von [dieser Antwort auf StackOverflow][so-answer] von
Konstantin_
Sinatra erlegt kein Nebenläufigkeitsmodell auf, sondern überlässt dies dem
selbst gewählten Rack-Proxy (Server), so wie Thin, Puma oder WEBrick.
Sinatra selbst ist Thread-sicher, somit ist es kein Problem wenn der
Rack-Proxy ein anderes Threading-Modell für Nebenläufigkeit benutzt.
Das heißt, dass wenn der Server gestartet wird, dass man die korrekte
Aufrufsmethode benutzen sollte für den jeweiligen Rack-Proxy.
Das folgende Beispiel ist eine Veranschaulichung eines mehrprozessigen
Thin Servers:
``` ruby
# app.rb
require 'sinatra/base'
class App < Sinatra::Base
get '/' do
"Hello, World"
end
end
App.run!
```
Um den Server zu starten, führt man das folgende Kommando aus:
``` shell
thin --threaded start
```
[so-answer]: http://stackoverflow.com/questions/6278817/is-sinatra-multi-threaded/6282999#6282999)
## Systemanforderungen
Die folgenden Versionen werden offiziell unterstützt:
- Ruby 2.2
-
2.2 wird vollständig unterstützt. Es gibt derzeit keine Pläne die
offizielle Unterstützung zu beenden
- 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).
Versionen vor Ruby 2.2.2 werden ab Sinatra 2.0 nicht länger unterstützt.
Nachfolgende Ruby-Versionen werden regelmäßig auf Unterstützung geprüft.
Die nachfolgend aufgeführten Ruby-Implementierungen werden offiziell nicht von
Sinatra unterstützt, funktionieren aber normalerweise:
* Ruby Enterprise Edition
* Ältere Versionen von JRuby und Rubinius
* MacRuby, Maglev, IronRuby
* Ruby 1.9.0 und 1.9.1 (wird aber nicht empfohlen)
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
(zukünftige Versionen von MRI) mit eingebunden. Es kann davon ausgegangen
werden, dass Sinatra MRI auch weiterhin vollständig unterstützen wird.
Sinatra sollte auf jedem Betriebssystem laufen, das einen funktionierenden
Ruby-Interpreter aufweist.
Sinatra läuft aktuell nicht unter Cardinal, SmallRuby, BlueRuby oder Ruby <=
2.2.
## 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://bundler.io) 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...
```
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
```
## Versions-Verfahren
Sinatra folgt dem sogenannten [Semantic Versioning](http://semver.org/), d.h.
SemVer und SemVerTag.
## Mehr
* [Projekt-Website](http://www.sinatrarb.com/) - Ergänzende Dokumentation,
News und Links zu anderen Ressourcen.
* [Mitmachen](http://www.sinatrarb.com/contributing.html) - Einen Fehler
gefunden? Brauchst du Hilfe? Hast du einen Patch?
* [Issue-Tracker](https://github.com/sinatra/sinatra/issues)
* [Twitter](https://twitter.com/sinatra)
* [Mailing-Liste](http://groups.google.com/group/sinatrarb)
* IRC [#sinatra](irc://chat.freenode.net/#sinatra) auf
http://freenode.net Es gibt dort auch immer wieder deutschsprachige
Entwickler, die gerne weiterhelfen.
* [Sinatra & Friends](https://sinatrarb.slack.com) on Slack and see
[here](https://sinatra-slack.herokuapp.com/) for an invite.
* [Sinatra Book](https://github.com/sinatra/sinatra-book/) Kochbuch Tutorial
* [Sinatra Recipes](http://recipes.sinatrarb.com/) Sinatra-Rezepte aus der
Community
* API Dokumentation für die
[aktuelle Version](http://www.rubydoc.info//gems/sinatra) oder für
[HEAD](http://www.rubydoc.info/github/sinatra/sinatra) auf
http://rubydoc.info
* [CI Server](https://travis-ci.org/sinatra/sinatra)
sinatra-2.0.8.1/README.es.md 0000664 0000000 0000000 00000246564 13603175240 0015230 0 ustar 00root root 0000000 0000000 # Sinatra
[](http://travis-ci.org/sinatra/sinatra)
Sinatra es un
[DSL](https://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
```
Instala la gema:
```shell
gem install sinatra
ruby miapp.rb
```
Y corre la aplicación:
```shell
gem install sinatra
ruby miapp.rb
```
Ver en [http://localhost:4567](http://localhost:4567).
El código que cambiaste no tendra efecto hasta que reinicies el servidor.
Por favor reinicia el servidor cada vez que cambies tu código o usa [sinatra/reloader](http://www.sinatrarb.com/contrib/reloader).
Se recomienda ejecutar `gem install thin`, porque Sinatra lo utilizará si está disponible.
## Tabla de Contenidos
* [Sinatra](#sinatra)
* [Tabla de Contenidos](#tabla-de-contenidos)
* [Rutas](#rutas)
* [Condiciones](#condiciones)
* [Valores de Retorno](#valores-de-retorno)
* [Comparadores de Rutas Personalizados](#comparadores-de-rutas-personalizados)
* [Archivos Estáticos](#archivos-estáticos)
* [Vistas / Plantillas](#vistas--plantillas)
* [Plantillas Literales](#plantillas-literales)
* [Lenguajes de Plantillas Disponibles](#lenguajes-de-plantillas-disponibles)
* [Plantillas Haml](#plantillas-haml)
* [Plantillas Erb](#plantillas-erb)
* [Plantillas Builder](#plantillas-builder)
* [Plantillas Nokogiri](#plantillas-nokogiri)
* [Plantillas Sass](#plantillas-sass)
* [Plantillas SCSS](#plantillas-scss)
* [Plantillas Less](#plantillas-less)
* [Plantillas Liquid](#plantillas-liquid)
* [Plantillas Markdown](#plantillas-markdown)
* [Plantillas Textile](#plantillas-textile)
* [Plantillas RDoc](#plantillas-rdoc)
* [Plantillas AsciiDoc](#plantillas-asciidoc)
* [Plantillas Radius](#plantillas-radius)
* [Plantillas Markaby](#plantillas-markaby)
* [Plantillas RABL](#plantillas-rabl)
* [Plantillas Slim](#plantillas-slim)
* [Plantillas Creole](#plantillas-creole)
* [Plantillas MediaWiki](#mediawiki-templates)
* [Plantillas CofeeScript](#plantillas-coffeescript)
* [Plantillas Stylus](#plantillas-stylus)
* [Plantillas Yajl](#plantillas-yajl)
* [Plantillas Wlang](#plantillas-wlang)
* [Accediendo Variables en Plantillas](#accediendo-a-variables-en-plantillas)
* [Plantillas con `yield` y `layout` anidado](#plantillas-con-yield-y-layout-anidado)
* [Plantillas Inline](#plantillas-inline)
* [Plantillas Nombradas](#plantillas-nombradas)
* [Asociando Extensiones de Archivo](#asociando-extensiones-de-archivo)
* [Añadiendo Tu Propio Motor de Plantillas](#añadiendo-tu-propio-motor-de-plantillas)
* [Usando Lógica Personalizada para la Búsqueda en Plantillas](#usando-lógica-personalizada-para-la-búsqueda-en-plantillas)
* [Filtros](#filtros)
* [Helpers](#helpers)
* [Usando Sesiones](#usando-sesiones)
* [Secreto de Sesión](#secreto-de-sesión)
* [Configuración de Sesión](#configuración-de-sesión)
* [Escogiendo tu propio Middleware de Sesión](#escogiendo-tu-propio-middleware-de-sesión)
* [Interrupcion](#interrupción)
* [Paso](#paso)
* [Desencadenando Otra Ruta](#desencadenando-otra-ruta)
* [Configurando el Cuerpo, Código de Estado y los Encabezados](#configurando-el-cuerpo-código-de-estado-y-los-encabezados)
* [Streaming De Respuestas](#streaming-de-respuestas)
* [Logging](#logging)
* [Tipos Mime](#tipos-mime)
* [Generando URLs](#generando-urls)
* [Redirección del Navegador](#redirección-del-navegador)
* [Control del Cache](#control-del-cache)
* [Enviando Archivos](#enviando-archivos)
* [Accediendo al Objeto Request](#accediendo-al-objeto-request)
* [Archivos Adjuntos](#archivos-adjuntos)
* [Fecha y Hora](#fecha-y-hora)
* [Buscando los Archivos de las Plantillas](#buscando-los-archivos-de-las-plantillas)
* [Configuración](#configuración)
* [Configurando la Protección Contra Ataques](#configurando-la-protección-contra-ataques)
* [Configuraciones Disponibles](#configuraciones-disponibles)
* [Entornos](#entornos)
* [Manejo de Errores](#manejo-de-errores)
* [Not Found](#not-found)
* [Error](#error)
* [Rack Middleware](#rack-middleware)
* [Pruebas](#pruebas)
* [Sinatra::Base - Middleware, Librerías, y Aplicaciones Modulares](#sinatrabase---middleware-librerías-y-aplicaciones-modulares)
* [Estilo Modular vs Estilo Clásico](#estilo-modular-vs-clásico)
* [Sirviendo una Aplicación Modular](#sirviendo-una-aplicación-modular)
* [Usando una Aplicación de Estilo Clásico con config.ru](#usando-una-aplicación-clásica-con-un-archivo-configru)
* [¿Cuándo usar config.ru?](#cuándo-usar-configru)
* [Utilizando Sinatra como Middleware](#utilizando-sinatra-como-middleware)
* [Creación Dinámica de Aplicaciones](#creación-dinámica-de-aplicaciones)
* [Ámbitos y Ligaduras (Scopes and Binding)](#Ámbitos-y-ligaduras)
* [Alcance de una Aplicación/Clase](#Ámbito-de-aplicaciónclase)
* [Alcance de una Solicitud/Instancia](#Ámbito-de-peticióninstancia)
* [Alcance de Delegación](#Ámbito-de-delegación)
* [Línea de comandos](#línea-de-comandos)
* [Multi-threading](#multi-threading)
* [Requerimientos](#requerimientos)
* [A la Vanguardia](#a-la-vanguardia)
* [Usando bundler](#usando-bundler)
* [Versionado](#versionado)
* [Lecturas Recomendadas](#lecturas-recomendadas)
## Rutas
En Sinatra, una ruta es un método HTTP junto a un patrón de un URL.
Cada ruta está asociada a 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
link '/' do
.. afiliar a algo ..
end
unlink '/' do
.. separar 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.
Las rutas con barras al final son distintas a las que no tienen:
```ruby
get '/foo' do
# no es igual que "GET /foo/"
end
```
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 puede 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/to/file", "xml"]
end
```
Rutas con Expresiones Regulares:
```ruby
get /\/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
```
Las rutas también pueden usar parámetros de consulta:
```ruby
get '/posts' do
# es igual que "GET /posts?title=foo&author=bar"
title = params['title']
author = params['author']
# usa las variables title y author; la consulta es opcional para la ruta /posts
end
```
A propósito, a menos que desactives la protección para el ataque *path
traversal* (ver más [abajo](#configurando-la-protección-contra-ataques)), el path de la petición puede ser modificado
antes de que se compare con los de tus rutas.
Puedes perzonalizar las opciones de [Mustermann](https://github.com/sinatra/mustermann) usadas para una ruta pasando
el hash `:mustermann_opts`:
```ruby
get '\A/posts\z', :mustermann_opts => { :type => :regexp, :check_anchors => false } do
# es exactamente igual a /posts, con anclaje explícito
"¡Si igualas un patrón anclado aplaude!"
end
```
## 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 navegadores 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
```
`provides` busca el encabezado Accept de la solicitud
Puede definir sus 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
```
Para una condición que toma multiples valores usa splat:
```ruby
set(:autorizar) do |*roles| # <- mira el splat
condition do
unless sesion_iniciada? && roles.any? {|rol| usuario_actual.tiene_rol? rol }
redirect "/iniciar_sesion/", 303
end
end
end
get "/mi/cuenta/", :autorizar => [:usuario, :administrador] do
"Detalles de mi cuenta"
end
get "/solo/administradores/", :autorizar => :administrador do
"Únicamente para administradores!"
end
```
## Valores de Retorno
El valor de retorno de un bloque de ruta determina por lo menos el cuerpo de la respuesta
transmitida al cliente HTTP o por lo menos 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.
Puedes retornar 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 (Integer), cabeceras (Hash), cuerpo de
la respuesta (responde a #each)]`
* Un arreglo con dos elementos: `[estado (Integer), cuerpo de la respuesta
(responde a #each)]`
* Un objeto que responde a `#each` y que le pasa únicamente strings al bloque
dado
* Un Integer representando el código de estado
De esa manera, por ejemplo, podemos fácilmente implementar un ejemplo de streaming:
```ruby
class Stream
def each
100.times { |i| yield "#{i}\n" }
end
end
get('/') { Stream.new }
```
También puedes usar el `stream` helper ([descrito abajo](#streaming-de-respuestas))
para reducir el código repetitivo e incrustar la lógica de stream a la ruta
## Comparadores de Rutas Personalizados
Como se mostró anteriormente, Sinatra permite utilizar strings y expresiones
regulares para definir las rutas. Sin embargo, no termina ahí.Puedes
definir tus propios comparadores muy fácilmente:
```ruby
class TodoMenosElPatron
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)
TodoMenosElPatron.new(patron)
end
get cualquiera_menos("/index") do
# ...
end
```
Tenga 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 look ahead negativo:
```ruby
get %r{(?!/index)} do
# ...
end
```
## Archivos Estáticos
Los archivos estáticos son servidos desde el directorio público
`./public`. Puede especificar una ubicación diferente ajustando la
opción `:public_folder`:
```ruby
set :public_folder, File.dirname(__FILE__) + '/static'
```
Note 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`.
Use la configuración `:static_cache_control` para agregar el encabezado
`Cache-Control` (Ver mas [abajo](#control-del-cache)).
## 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 puedes 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` incrustado en `views/post.erb` (por
defecto, la plantilla `:index` es incrustada 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, puede 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 Symbol 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
- layout_options
-
Opciones especiales usadas únicamente para renderizar el layout. Ejemplo:
set :rdoc, :layout_options => { :views => 'views/layouts' }
Se asume que las plantillas están ubicadas directamente bajo el directorio `./views`.
Para usar un directorio diferente:
```ruby
set :views, settings.root + '/templates'
```
Es importante acordarse que siempre tienes que referenciar a las plantillas con
símbolos, incluso cuando se encuentran en un subdirectorio (en este caso
tienes que usar: `:'subdir/plantilla'` o `'subdir/plantilla'.to_sym`). Esto es debido
a que los métodos de renderización van a renderizar directamente cualquier string que
se les pase como argumento.
### Plantillas Literales
```ruby
get '/' do
haml '%div.titulo Hola Mundo'
end
```
Renderiza el string de la plantilla. Opcionalmente puedes especificar
`:path` y `:line` para un backtrace más claro si hay una ruta del sistema
de archivos o una línea asociada con ese string
```ruby
get '/' do
haml '%div.titulo Hola Mundo', :path => 'ejemplos/archivo.haml', :line => 3
end
```
### 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" } |
También toma un bloque para plantillas inline (ver [ejemplo](#plantillas-inline)).
#### Plantillas Nokogiri
Dependencias |
nokogiri |
Extensiones de Archivo |
.nokogiri |
Ejemplo |
nokogiri { |xml| xml.em "hola" } |
También toma un bloque para plantillas inline (ver [ejemplo](#plantillas-inline)).
#### 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 va a poder llamar a métodos de Ruby (excepto por `yield`) desde una
plantilla Liquid, casi siempre va a querer pasarle locales.
#### Plantillas Markdown
No es posible llamar métodos desde markdown, ni pasarle locales. Por lo tanto,
generalmente va a usarlo en combinación con otro motor de renderizado:
```ruby
erb :resumen, :locals => { :texto => markdown(:introduccion) }
```
Tenga en cuenta que también puedes llamar al método `markdown` desde otras
plantillas:
```ruby
%h1 Hola Desde Haml!
%p= markdown(:saludos)
```
Como no puede utilizar Ruby desde Markdown, no puede 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 puedes llamar al método `textile` desde otras
plantillas:
```ruby
%h1 Hola Desde Haml!
%p= textile(:saludos)
```
Como no puedes utilizar Ruby desde Textile, no puedes 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 puedes llamar al método `rdoc` desde otras
plantillas:
```ruby
%h1 Hola Desde Haml!
%p= rdoc(:saludos)
```
Como no puedes utilizar Ruby desde RDoc, no puedes 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 AsciiDoc
Dependencia |
Asciidoctor |
Extensiones de Archivo |
.asciidoc, .adoc and .ad |
Ejemplo |
asciidoc :README, :layout_engine => :erb |
Desde que no se puede utilizar métodos de Ruby desde una
plantilla AsciiDoc, casi siempre va a querer pasarle locales.
#### Plantillas Radius
Dependencias |
Radius |
Extensiones de Archivo |
.radius |
Ejemplo |
radius :index, :locals => { :clave => 'valor' } |
Desde que no se puede utilizar métodos de Ruby (excepto por `yield`) de una
plantilla Radius, casi siempre se necesita pasar locales.
#### Plantillas Markaby
Dependencias |
Markaby |
Extensiones de Archivo |
.mab |
Ejemplo |
markaby { h1 "Bienvenido!" } |
También toma un bloque para plantillas inline (ver [ejemplo](#plantillas-inline)).
#### 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 va a usarlo en combinación con otro motor de renderizado:
```ruby
erb :resumen, :locals => { :texto => cerole(:introduccion) }
```
Debe tomar en cuenta que también puede llamar al método `creole` desde otras
plantillas:
```ruby
%h1 Hola Desde Haml!
%p= creole(:saludos)
```
Como no puedes utilizar Ruby desde Creole, no puedes usar layouts escritos en
Creole. De todos modos, es posible usar un motor de renderizado para el layout
distinto al de la plantilla pasando la opción `:layout_engine`.
#### MediaWiki Templates
Dependencia |
WikiCloth |
Extension de Archivo |
.mediawiki and .mw |
Ejemplo |
mediawiki :wiki, :layout_engine => :erb |
No es posible llamar métodos desde el markup de MediaWiki, ni pasar locales al mismo.
Por lo tanto usualmente lo usarás en combinación con otro motor de renderizado:
```ruby
erb :overview, :locals => { :text => mediawiki(:introduction) }
```
Nota que también puedes llamar al método `mediawiki` desde dentro de otras plantillas:
```ruby
%h1 Hello From Haml!
%p= mediawiki(:greetings)
```
Debido a que no puedes llamar a Ruby desde MediaWiki, no puedes usar los diseños escritos en MediaWiki.
De todas maneras, es posible usar otro motor de renderizado para esa plantilla pasando la opción :layout_engine.
#### Plantillas CoffeeScript
#### Plantillas Stylus
Antes de poder usar las plantillas de Stylus, necesitas cargar `stylus` y `stylus/tilt`:
```ruby
require 'sinatra'
require 'stylus'
require 'stylus/tilt'
get '/' do
stylus :example
end
```
#### 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 contenido = {"foo":"bar","baz":"qux"};
present(contenido);
```
#### 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.
### 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 con `yield` y `layout` anidado
Un layout es usualmente una plantilla que llama a `yield`.
Dicha plantilla puede ser usada tanto a travé de la opción `:template`
como describimos arriba, o puede ser rederizada con un bloque como a
continuación:
```ruby
erb :post, :layout => false do
erb :index
end
```
Este código es principalmente equivalente a `erb :index, :layout => :post`.
Pasar bloques a métodos de renderizado es la forma mas útil de crear layouts anidados:
```ruby
erb :main_layout, :layout => false do
erb :admin_layout do
erb :user
end
end
```
Esto también se puede hacer en menos líneas de código con:
```ruby
erb :admin_layout, :layout => :main_layout do
erb :user
end
```
Actualmente, los siguientes métodos de renderizado aceptan un bloque: `erb`, `haml`,
`liquid`, `slim `, `wlang`. También el método general de `render` acepta un bloque.
### Plantillas Inline
Las plantillas pueden ser definidas al final del archivo fuente:
```ruby
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 tienes 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.Puedes 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, usa
`Tilt.register`. Por ejemplo, si quieres usar la extensión `tt` para
las plantillas Textile, puedes hacer lo siguiente:
```ruby
Tilt.register :tt, Tilt[:textile]
```
### Añadiendo Tu Propio Motor de Plantillas
Primero, registra tu motor con Tilt, y después, creá tu método de renderizado:
```ruby
Tilt.register :mipg, MiMotorDePlantilla
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.
### Usando Lógica Personalizada para la Búsqueda en Plantillas
Para implementar tu propio mecanismo de búsqueda de plantillas puedes
escribir tu propio método `#find_template`
```ruby
configure do
set :views [ './views/a', './views/b' ]
end
def find_template(views, name, engine, &block)
Array(views).each do |v|
super(v, name, engine, &block)
end
end
```
## 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
```
## Helpers
Usá el método top-level `helpers` para definir métodos ayudantes (helpers) 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
helpers 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
```
#### Secreto de Sesión
Para mejorar la seguridad, los datos de la sesión en la cookie se firman con un secreto usando `HMAC-SHA1`. El secreto de esta sesión debería ser de manera óptima
un valor aleatorio criptográficamente seguro de una longitud adecuada para
`HMAC-SHA1` que es mayor o igual que 64 bytes (512 bits, 128 hex caracteres).
Se le aconsejará que no use un secreto que sea inferior a 32
bytes de aleatoriedad (256 bits, 64 caracteres hexadecimales).
Por lo tanto, es **muy importante** que no solo invente el secreto,
sino que use un generador de números aleatorios para crearlo.
Los humanos somos extremadamente malos generando valores aleatorios
De forma predeterminada, un secreto de sesión aleatorio seguro de 32 bytes se genera para usted por
Sinatra, pero cambiará con cada reinicio de su aplicación. Si tienes varias
instancias de tu aplicación y dejas que Sinatra genere la clave, cada instancia
tendría una clave de sesión diferente y probablemente no es lo que quieres.
Para una mejor seguridad y usabilidad es
[recomendado](https://12factor.net/config) que genere un secreto de sesión
aleatorio seguro y se guarde en las variables de entorno en cada host que ejecuta
su aplicación para que todas las instancias de su aplicación compartan el mismo
secreto. Debería rotar periódicamente esta sesión secreta a un nuevo valor.
Aquí hay algunos ejemplos de cómo puede crear un secreto de 64 bytes y configurarlo:
**Generación de Secreto de Sesión**
```text
$ ruby -e "require 'securerandom'; puts SecureRandom.hex(64)"
99ae8af...snip...ec0f262ac
```
**Generación de Secreto de Sesión (Puntos Extras)**
Usa la [gema sysrandom](https://github.com/cryptosphere/sysrandom) para preferir
el uso de el sistema RNG para generar valores aleatorios en lugar de
espacio de usuario `OpenSSL` que MRI Ruby tiene por defecto:
```text
$ gem install sysrandom
Building native extensions. This could take a while...
Successfully installed sysrandom-1.x
1 gem installed
$ ruby -e "require 'sysrandom/securerandom'; puts SecureRandom.hex(64)"
99ae8af...snip...ec0f262ac
```
**Secreto de Sesión en Variable de Entorno**
Establezca una variable de entorno `SESSION_SECRET` para Sinatra en el valor que
generaste. Haz que este valor sea persistente durante los reinicios de su host. El
método para hacer esto variará a través de los sistemas operativos, esto es para
propósitos ilustrativos solamente:
```bash
# echo "export SESSION_SECRET=99ae8af...snip...ec0f262ac" >> ~/.bashrc
```
**Configuración de la Aplicación y su Secreto de Sesión**
Configura tu aplicación a prueba de fallas si la variable de entorno
`SESSION_SECRET` no esta disponible
Para puntos extras usa la [gema sysrandom](https://github.com/cryptosphere/sysrandom) acá tambien:
```ruby
require 'securerandom'
# -or- require 'sysrandom/securerandom'
set :session_secret, ENV.fetch('SESSION_SECRET') { SecureRandom.hex(64) }
```
#### Configuración de Sesión
Si desea configurarlo más, también puede almacenar un hash con opciones
en la configuración `sessions`:
```ruby
set :sessions, :domain => 'foo.com'
```
Para compartir su sesión en otras aplicaciones en subdominios de foo.com, agregue el prefijo
dominio con un *.* como este en su lugar:
```ruby
set :sessions, :domain => '.foo.com'
```
#### Escogiendo tu Propio Middleware de Sesión
Tenga en cuenta que `enable :sessions` en realidad almacena todos los datos en una cookie.
Esto no siempre podria ser lo que quieres (almacenar muchos datos aumentará su
tráfico, por ejemplo). Puede usar cualquier middleware de sesión proveniente de Rack
para hacerlo, se puede utilizar uno de los siguientes métodos:
```ruby
enable :sessions
set :session_store, Rack::Session::Pool
```
O para configurar sesiones con un hash de opciones:
```ruby
set :sessions, :expire_after => 2592000
set :session_store, Rack::Session::Pool
```
Otra opción es **no** llamar a `enable :sessions`, sino
su middleware de elección como lo haría con cualquier otro middleware.
Es importante tener en cuenta que al usar este método, la protección
de sesiones **no estará habilitada por defecto**.
También será necesario agregar el middleware de Rack para hacer eso:
```ruby
use Rack::Session::Pool, :expire_after => 2592000
use Rack::Protection::RemoteToken
use Rack::Protection::SessionHijacking
```
Mira '[Configurando la protección contra ataques](#configurando-la-protección-contra-ataques)' para mas información.
### Interrupción
Para detener inmediatamente una petición dentro de un filtro o una ruta debes usar:
```ruby
halt
```
También puedes 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.
### Desencadenando Otra Ruta
A veces, `pass` no es lo que quieres, sino obtener el
resultado de la llamada a otra ruta. Simplemente use `call` para lograr esto:
```ruby
get '/foo' do
status, headers, body = call env.merge("PATH_INFO" => '/bar')
[status, headers, body.map(&:upcase)]
end
get '/bar' do
"bar"
end
```
Nota 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 quieres que la petición se envíe a la misma instancia de la aplicación en
lugar de otra, usá `call!` en lugar de `call`.
En la especificación de Rack puedes encontrar más información sobre
`call`.
### Configurando el Cuerpo, Código de Estado y los Encabezados
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. Sin embargo, en algunos
escenarios puede que sea conveniente asignar el cuerpo en un punto arbitrario
del flujo de ejecución con el método helper `body`. A partir de ahí, puedes 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 (puedes usar esto para implementar streaming, mira ["Valores de Retorno"](#valores-de-retorno)).
De manera similar, también puedes 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`, `status` y `headers` sin agregarles argumentos pueden usarse
para acceder a sus valores actuales.
### 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 helper `stream` 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
```
Puedes implementar APIs de streaming,
[Server-Sent Events](https://html.spec.whatwg.org/multipage/server-sent-events.html#server-sent-events) y puede ser usado
como base para [WebSockets](https://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
alojar la aplicación. Puede que algunos servidores 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. Permite que tu lo cierres 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
# long polling
set :server, :thin
connections = []
get '/subscribe' do
# registrar a un cliente interesado en los eventos del servidor
stream(:keep_open) do |out|
connections << out
# purgar conexiones muertas
connections.reject!(&:closed?)
end
end
post '/:message' do
connections.each do |out|
# notificar al cliente que ha llegado un nuevo mensaje
out << params['message'] << "\n"
# indicar al cliente para conectarse de nuevo
out.close
end
# reconocer
"message received"
end
```
También es posible que el cliente cierre la conexión cuando intenta
escribir en el socket. Debido a esto, se recomienda verificar con
`out.closed?` antes de intentar escribir.
### Logging
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 quieres 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 puedes usar con el helper `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
Puedes 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://www.google.com/', 'te confundiste de lugar, compañero'
```
También puedes 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, puedes 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
```
### Control del Cache
Asignar tus encabezados correctamente es el cimiento para realizar un cacheo
HTTP correcto.
Puedes 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 asistentes *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](https://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)
y potentes (como put) que el recurso existe, mientras que para el resto
(como post) asume que no. Puedes cambiar este comportamiento con la opción
`:new_resource`:
```ruby
get '/crear' do
etag '', :new_resource => true
Articulo.create
erb :nuevo_articulo
end
```
Si quieres seguir usando una weak ETag, indicalo con la opción `:kind`:
```ruby
etag '', :new_resource => true, :kind => :weak
```
### Enviando Archivos
Para enviar archivos, puedes 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 y :inline
- length
- Encabezado Content-Length, por defecto toma el tamaño del archivo
- status
-
Código de estado a enviar. Útil cuando se envía un archivo estático como un error página. Si es compatible con el controlador de Rack, otros medios que no sean la transmisión del proceso de Ruby será utilizado. Si usas este método de ayuda, Sinatra manejará automáticamente las solicitudes de rango.
### Accediendo al objeto Request
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 navegador
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)
request.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
Puedes usar el 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 puedes 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 puedes 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 anterior si escribís un método extraño.
## 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 puedes tener configuraciones dinámicas usando bloques
set(:css_dir) { File.join(views, 'css') }
end
```
Ejecutar únicamente cuando el entorno (la variable de entorno APP_ENV) es
`:production`:
```ruby
configure :production do
...
end
```
Ejecutar cuando el entorno es `:production` o `:test`:
```ruby
configure :production, :test do
...
end
```
Puedes 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 Contra Ataques
Sinatra usa [Rack::Protection](https://github.com/sinatra/sinatra/tree/master/rack-protection#readme)
para defender a tu aplicación de los ataques más comunes. Si por algún motivo,
quieres desactivar esta funcionalidad, puedes 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_charset
-
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_charset << "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 o localhost
si su `environment` está configurado para desarrollo).
- 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['APP_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.
- mustermann_opts
-
Un hash predeterminado de opciones para pasar a Mustermann.new
al compilar las rutas.
- 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 se habilitan o no las protecciones de ataques web.
Ver la sección de protección encima.
- public_folder
-
Lugar 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.
- quiet
-
Inhabilita los logs generados por los comandos de inicio y detención de Sinatra.
false por defecto.
- reload_templates
-
Define Si se vuelven a cargar plantillas entre las solicitudes o no. Habilitado en
modo de desarrollo.
- root
-
Lugar 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á ejecutándose, ¡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.
- server_settings
-
Si está utilizando un servidor web WEBrick, presumiblemente para su entorno de desarrollo, puede pasar un hash de opciones a server_settings , como SSLEnable o SSLVerifyClient . Sin embargo, los servidores web como Puma y Thin no son compatibles, por lo que puede establecer server_settings definiéndolo como un método cuando llame a configure .
- 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.
- session_store
-
Define el middleware de sesión Rack utilizado. Predeterminado a
Rack::Session::Cookie. Consulte la sección 'Uso de sesiones' para obtener más información.
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.
-
También se puede establecer en :after_handler para activar la aplicación especificada
que hara el manejo de errores antes de mostrar un stack trace en el navegador.
- static
-
- Define si Sinatra debe servir los archivos estáticos.
- Deshabilitar cuando se utiliza un servidor capaz de hacer esto por su cuenta.
- La desactivación aumentará el rendimiento.
-
Habilitado por defecto en estilo clásico, deshabilitado para aplicaciones modulares.
- static_cache_control
-
Cuando Sinatra está sirviendo archivos estáticos, y
esta 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].
- threaded
-
Si se establece en true , le dirá a Thin que use
EventMachine.defer para procesar la solicitud.
- traps
- Define si Sinatra debería manejar las señales del sistema.
- views
-
Path del directorio de las vistas. Si no está presente,
se infiere del valor de la opción app_file.
- x_cascade
-
Establece o no el encabezado de X-Cascade si no hay una coincidencia de ruta.
verdadero por defecto.
## 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 `APP_ENV`:
```shell
APP_ENV=production ruby my_app.rb
```
Los métodos `development?`, `test?` y `production?` te permiten conocer el
entorno actual.
```ruby
get '/' do
if settings.development?
"development!"
else
"not development!"
end
end
```
## Manejo de Errores
Los manejadores de errores se ejecutan dentro del mismo contexto que las rutas
y los filtros `before`, lo que significa que puedes usar, por ejemplo,
`haml`, `erb`, `halt`, etc.
### 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'].message
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
```
Obtienes esto:
```
Lo que pasó fue... algo malo
```
También, puedes 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" y se muestran
en tu navegador para que tengas información adicional sobre el error.
## Rack Middleware
Sinatra corre sobre [Rack](http://rack.github.io/), 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://www.rubydoc.info/github/rack/rack/master/Rack/Builder) (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 usualmente no tengas que usarlas (con `use`) explícitamente.
Puedes encontrar middleware útil en
[rack](https://github.com/rack/rack/tree/master/lib/rack),
[rack-contrib](https://github.com/rack/rack-contrib#readme),
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://www.rubydoc.info/github/brynary/rack-test/master/frames):
```ruby
require 'mi_app_sinatra'
require 'minitest/autorun'
require 'rack/test'
class MiAppTest < Minitest::Test
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
```
Nota: Si está utilizando Sinatra en el estilo modular, reemplace
`Sinatra::Application` anterior con el nombre de clase de su aplicación.
## Sinatra::Base - Middleware, Librerías, y Aplicaciones Modulares
Definir tu aplicación en el nivel superior 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 alto nivel
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://www.sinatrarb.com/configuration.html)
para detalles sobre las opciones disponibles y su comportamiento.
Si quieres un comportamiento más similar
a cuando defines tu aplicación en el nivel superior (también conocido como Clásico)
estilo), puede subclase `Sinatra::Aplicación`
```ruby
require 'sinatra/base'
class MiAplicacion < Sinatra::Application
get '/' do
'Hola Mundo!'
end
end
```
### 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.
La desventaja de usar el estilo clásico en lugar del modular consiste en que
solamente puedes tener una aplicación Sinatra por proceso Ruby. Si tienes
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 (sútiles) entre las configuraciones
de ambos estilos:
Configuración |
Clásica |
Modular |
Modular |
app_file |
archivo que carga sinatra |
archivo con la subclase de Sinatra::Base |
archivo con la subclase Sinatra::Application |
run |
$0 == app_file |
false |
false |
logging |
true |
false |
true |
method_override |
true |
false |
true |
inline_templates |
true |
false |
true |
static |
true |
File.exist?(public_folder) |
true |
### 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 quieres usar `config.ru`:
* Quieres realizar el deploy con un handler Rack distinto (Passenger, Unicorn,
Heroku, ...).
* Quieres usar más de una subclase de `Sinatra::Base`.
* Quieres usar Sinatra únicamente para middleware, pero no como un endpoint.
No hay necesidad de utilizar un archivo `config.ru` exclusivamente
porque tienes 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 tienes `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 encuentras 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 tienes métodos como `get` o `before`, pero
no puedes 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
```
Tienes 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`
* El bloque pasado a `Sinatra.new`
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 puedes acceder a los objetos `request` y `session` o llamar a los métodos
de renderización como `erb` o `haml`. Puedes 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
```
Tienes la ligadura al ámbito de la petición dentro de:
* bloques pasados a get, head, post, put, delete, options, patch, link y unlink
* filtros before/after
* métodos helpers
* 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 tienes 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:
tienes un `self` diferente). Puedes agregar delegaciones de método llamando a
`Sinatra::Delegator.delegate :nombre_del_metodo`.
Tienes é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 myapp.rb [-h] [-x] [-q] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER]
```
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)
-q # activar el modo silecioso para el servidor (está desactivado por defecto)
-x # activa el mutex lock (está desactivado por defecto)
```
### Multi-threading
_Basado en [esta respuesta en StackOverflow](http://stackoverflow.com/questions/6278817/is-sinatra-multi-threaded/6282999#6282999) escrita por Konstantin_
Sinatra no impone ningún modelo de concurrencia, sino que lo deja en manos del
handler Rack que se esté usando (Thin, Puma, WEBrick). Sinatra en sí mismo es
thread-safe, así que no hay problema en que el Rack handler use un modelo de
concurrencia basado en hilos.
Esto significa que, cuando estemos arrancando el servidor, tendríamos que
especificar la opción adecuada para el handler Rack específico. En este ejemplo
vemos cómo arrancar un servidor Thin multihilo:
```ruby
# app.rb
require 'sinatra/base'
class App < Sinatra::Base
get '/' do
"¡Hola, Mundo!"
end
end
App.run!
```
Para arrancar el servidor, el comando sería:
```shell
thin --threaded start
```
## Requerimientos
Las siguientes versiones de Ruby son soportadas oficialmente:
- Ruby 2.2
-
2.2 Es totalmente compatible y recomendado. Actualmente no hay planes
soltar el apoyo oficial para ello.
- Rubinius
-
Rubinius es oficialmente compatible (Rubinius> = 2.x). Se recomienda instalar la gema puma
gem install puma.
- JRuby
-
La última versión estable de JRuby es oficialmente compatible. No lo es
recomienda usar extensiones C con JRuby. Se recomienda instalar la gema trinidad
gem install trinidad .
Las versiones de Ruby anteriores a 2.2.2 ya no son compatibles con Sinatra 2.0 .
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 ser soportada oficialmente, significa que si las cosas se rompen
ahí y no en una plataforma soportada, asumimos que no es nuestro problema sino
el suyo.
También ejecutamos nuestro CI contra ruby-head (futuras versiones de MRI), pero
no puede garantizar nada, ya que se mueve constantemente. Esperar próxima
2.x versiones para ser totalmente compatibles.
Sinatra debería trabajar en cualquier sistema operativo compatible la implementación de Ruby
elegida
Si ejecuta MacRuby, debe `gem install control_tower`.
Sinatra actualmente no funciona con Cardinal, SmallRuby, BlueRuby o cualquier
versión de Ruby anterior a 2.2.
## A la Vanguardia
Si quieres usar el código de Sinatra más reciente, sientete libre de ejecutar
tu aplicación sobre la rama master, en general es bastante estable.
También liberamos prereleases de vez en cuando, así, puedes hacer:
```shell
gem install sinatra --pre
```
Para obtener algunas de las últimas características.
### Usando Bundler
Esta es la manera recomendada para ejecutar tu aplicación sobre la última
versión de Sinatra usando [Bundler](http://bundler.io).
Primero, instala 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
```
Ten en cuenta que tienes 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 puedes arrancar tu aplicación así:
```shell
bundle exec ruby miapp.rb
```
## 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?. ¿Necesitas ayuda?. ¿Tienes un parche?.
* [Seguimiento de problemas](https://github.com/sinatra/sinatra/issues)
* [Twitter](https://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 & Friends](https://sinatrarb.slack.com) en Slack y revisa
[acá](https://sinatra-slack.herokuapp.com/) Para una invitación.
* [Sinatra Book](https://github.com/sinatra/sinatra-book/) 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://www.rubydoc.info/gems/sinatra) o para la
[rama de desarrollo actual](http://www.rubydoc.info/github/sinatra/sinatra)
en http://www.rubydoc.info/
* [Servidor de CI](https://travis-ci.org/sinatra/sinatra)
sinatra-2.0.8.1/README.fr.md 0000664 0000000 0000000 00000236354 13603175240 0015224 0 ustar 00root root 0000000 0000000 # Sinatra
*Attention : Ce document correspond à la traduction de la version anglaise et
il n'est peut-être plus à jour.*
Sinatra est un [DSL](https://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 Sinatra :
```shell
gem install sinatra
```
Puis lancez votre programme :
```shell
ruby mon_application.rb
```
Le résultat est visible sur :
[http://localhost:4567](http://localhost:4567)
Il est recommandé d'exécuter également `gem install thin`, pour que
Sinatra utilise le server Thin quand il est disponible.
## Table des matières
* [Sinatra](#sinatra)
* [Table des matières](#table-des-matières)
* [Routes](#routes)
* [Conditions](#conditions)
* [Valeurs de retour](#valeurs-de-retour)
* [Masques de route spécifiques](#masques-de-route-spécifiques)
* [Fichiers statiques](#fichiers-statiques)
* [Vues / Templates](#vues--templates)
* [Templates littéraux](#templates-littéraux)
* [Langages de template disponibles](#langages-de-template-disponibles)
* [Templates 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)
* [Accéder aux variables dans un Template](#accéder-aux-variables-dans-un-template)
* [Templates avec `yield` et layouts imbriqués](#templates-avec-yield-et-layouts-imbriqués)
* [Templates dans le fichier source](#templates-dans-le-fichier-source)
* [Templates nommés](#templates-nommés)
* [Associer des extensions de fichier](#associer-des-extensions-de-fichier)
* [Ajouter son propre moteur de rendu](#ajouter-son-propre-moteur-de-rendu)
* [Filtres](#filtres)
* [Helpers](#helpers)
* [Utiliser les sessions](#utiliser-les-sessions)
* [Halt](#halt)
* [Passer](#passer)
* [Déclencher une autre route](#déclencher-une-autre-route)
* [Définir le corps, le code retour et les en-têtes](#définir-le-corps-le-code-retour-et-les-en-têtes)
* [Faire du streaming](#faire-du-streaming)
* [Journalisation (Logging)](#journalisation-logging)
* [Types Mime](#types-mime)
* [Former des URLs](#former-des-urls)
* [Redirection du navigateur](#redirection-du-navigateur)
* [Contrôle du cache](#contrôle-du-cache)
* [Envoyer des fichiers](#envoyer-des-fichiers)
* [Accéder à l'objet requête](#accéder-à-lobjet-requête)
* [Fichiers joints](#fichiers-joints)
* [Gérer Date et Time](#gérer-date-et-time)
* [Chercher les fichiers de templates](#chercher-les-fichiers-de-templates)
* [Configuration](#configuration)
* [Se protéger des attaques](#se-protéger-des-attaques)
* [Paramètres disponibles](#paramètres-disponibles)
* [Environements](#environements)
* [Gérer les erreurs](#gérer-les-erreurs)
* [NotFound](#notfound)
* [Error](#error)
* [Les Middlewares Rack](#les-middlewares-rack)
* [Tester](#tester)
* [Sinatra::Base - Les Middlewares, Bibliothèques, et Applications Modulaires](#sinatrabase---les-middlewares-bibliothèques-et-applications-modulaires)
* [Style modulaire vs. style classique](#style-modulaire-vs-style-classique)
* [Servir une application modulaire](#servir-une-application-modulaire)
* [Utiliser une application de style classique avec un fichier config.ru](#utiliser-une-application-de-style-classique-avec-un-fichier-configru)
* [Quand utiliser un fichier config.ru ?](#quand-utiliser-un-fichier-configru-)
* [Utiliser Sinatra comme Middleware](#utiliser-sinatra-comme-middleware)
* [Création dynamique d'applications](#création-dynamique-dapplications)
* [Contextes et Binding](#contextes-et-binding)
* [Contexte de l'application/classe](#contexte-de-lapplicationclasse)
* [Contexte de la requête/instance](#contexte-de-la-requêteinstance)
* [Le contexte de délégation](#le-contexte-de-délégation)
* [Ligne de commande](#ligne-de-commande)
* [Multi-threading](#multi-threading)
* [Configuration nécessaire](#configuration-nécessaire)
* [Essuyer les plâtres](#essuyer-les-plâtres)
* [Installer avec Bundler](#installer-avec-bundler)
* [Faire un clone local](#faire-un-clone-local)
* [Installer globalement](#installer-globalement)
* [Versions](#versions)
* [Mais encore](#mais-encore)
## 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
.. paramétrer quelque chose ..
end
link '/' do
.. relier quelque chose ..
end
unlink '/' do
.. séparer quelque chose ..
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|
# répond aux requêtes "GET /bonjour/foo" et "GET /bonjour/bar"
# params['nom'] est 'foo' ou 'bar'
# n contient params['nom']
"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 /\/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|
# répond à "GET /meta/bonjour/monde", "GET /bonjour/monde/1234" etc.
"Bonjour, #{c} !"
end
```
Les routes peuvent aussi comporter des paramètres optionnels :
```ruby
get '/articles/:format?' do
# répond à "GET /articles/" ou avec une extension "GET /articles/json", "GET /articles/xml" etc...
end
```
Ainsi que des paramètres d'URL :
```ruby
get '/articles' do
# répond à "GET /articles?titre=foo&auteur=bar"
titre = params['titre']
auteur = params['auteur']
# utilise les variables titre et auteur qui sont des paramètres d'URL optionnels pour la route /articles
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
```
`provides` se base sur l'en-tête `Accept` de la requête.
Vous pouvez facilement définir vos propres conditions :
```ruby
set(:chance) { |valeur| condition { rand <= valeur } }
get '/gagner_une_voiture', :chance => 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 (Integer), en-têtes (Hash), corps
de la réponse (répondant à #each)]`
* Un tableau de 2 élements : `[code statut (Integer), 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 Integer représentant le code statut
Ainsi, on peut facilement implémenter un exemple de streaming :
```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 les répétitions 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 plus compliqué qu'il ne devrait et peut être implémenté de la façon suivante :
```ruby
get // do
pass if request.path_info == "/index"
# ...
end
```
Ou bien en utilisant cette expression regulière :
```ruby
get %r{(?!/index)} do
# ...
end
```
## Fichiers statiques
Les fichiers du dossier `./public` sont servis de façon statique. Vous pouvez spécifier un autre dossier avec 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 accessible à 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 bas).
## Vues / Templates
Chaque 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 génère 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 hash d'options comme second argument :
```ruby
get '/' do
erb :index, :layout => :post
end
```
Ceci génèrera la vue `views/index.erb` en l'intégrant au *layout* `views/post.erb` (`views/layout.erb` est la valeur par défaut si 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 les options de chaque langage de template de façon
générale :
```ruby
set :haml, :format => html5
get '/' do
haml :index
end
```
Les arguments passés à 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
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 ou false).
Ou indique le template à utiliser lorsque c'est un symbole. Exemple :
erb :index, :layout => !request.xhr?.
- content_type
-
Content-Type que le template génère. La valeur par défaut dépend du langage de template.
- scope
-
Contexte dans 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
- layout_options
-
Options spécifiques à la génération du layout. Exemple : set :rdoc,
:layout_options => { :views => 'views/layouts' }
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.
### Templates littéraux
```ruby
get '/' do
haml '%div.title Bonjour le monde'
end
```
Utilisera la chaine de caractères comme template pour générer la réponse.
### 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
Il n’est pas possible d’appeler des méthodes depuis markdown, ni de
lui passer de variables locales. Par conséquent, il sera souvent utilisé
en combinaison avec un autre moteur de rendu :
```ruby
erb :accueil, :locals => { :text => markdown(:introduction) }
```
Notez que vous pouvez également appeler la méthode `markdown` depuis un autre template :
```ruby
%h1 Bonjour depuis Haml !
%p= markdown(:bienvenue)
```
Comme vous ne pouvez pas appeler de méthode Ruby depuis 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 de méthodes depuis textile, ni de lui
passer de variables locales. Par conséquent, il sera souvent utilisé en
combinaison avec un autre moteur de rendu :
```ruby
erb :accueil, :locals => { :text => textile(:introduction) }
```
Notez que vous pouvez également appeler la méthode `textile` depuis un autre template :
```ruby
%h1 Bonjour depuis Haml !
%p= textile(:bienvenue)
```
Comme vous ne pouvez pas appeler de méthode Ruby depuis 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 de méthodes Ruby depuis rdoc, ni de lui
passer de variables locales. Par conséquent, il sera souvent utilisé en
combinaison avec un autre moteur de rendu :
```ruby
erb :accueil, :locals => { :text => rdoc(:introduction) }
```
Notez que vous pouvez également appeler la méthode `rdoc` depuis un autre template :
```ruby
%h1 Bonjour depuis Haml !
%p= rdoc(:bienvenue)
```
Comme vous ne pouvez pas appeler de méthodes Ruby depuis 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 Lang |
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 de méthodes Ruby depuis creole, ni de lui
passer de variables locales. Par conséquent, il sera souvent utilisé en
combinaison avec un autre moteur de rendu :
```ruby
erb :accueil, :locals => { :text => markdown(:introduction) }
```
Notez que vous pouvez également appeler la méthode `creole` depuis un autre template :
```ruby
%h1 Bonjour depuis Haml !
%p= creole(:bienvenue)
```
Comme vous ne pouvez pas appeler de méthodes Ruby depuis Creole, vous ne pouvez
pas utiliser de layouts écrits en Creole. 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
#### Templates Stylus
Avant de pouvoir utiliser des templates Stylus, vous devez auparavant charger
`stylus` et `stylus/tilt` :
```ruby
require 'sinatra'
require 'stylus'
require 'stylus/tilt'
get '/' do
stylus :exemple
end
```
#### Templates Yajl
Dépendances |
yajl-ruby
|
Extensions de fichier |
.yajl |
Exemple |
yajl :index, :locals => { :key => 'qux' }, :callback => 'present', :variable => 'ressource'
|
La source du template est évaluée 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é.
```ruby
var ressource = {"foo":"bar","baz":"qux"}; present(ressource);
```
#### Templates WLang
Dépendances |
wlang |
Extensions de fichier |
.wlang |
Exemple |
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ée, via le passage de variables
locales. Il est néanmoins possible d’écrire un layout en wlang et d’y utiliser
`yield`.
### 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 nécessaire lorsque l'on veut utiliser un template depuis un autre template (partiel) et qu'il faut donc adapter le nom des variables.
### Templates avec `yield` et layouts imbriqués
En général, un layout est un simple template qui appelle `yield`. Ce genre de
template peut s'utiliser via l'option `:template` comme décrit précédemment ou
peut être rendu depuis un bloc :
```ruby
erb :post, :layout => false do
erb :index
end
```
Ce code est plus ou moins équivalent à `erb :index, :layout => :post`.
Le fait de passer des blocs aux méthodes de rendu est particulièrement utile
pour gérer des templates imbriqués :
```ruby
erb :layout_principal, :layout => false do
erb :layout_admin do
erb :utilisateur
end
end
```
Ou plus brièvement :
```ruby
erb :layout_admin, :layout => :layout_principal do
erb :utilisateur
end
```
Actuellement, les méthodes de rendu qui acceptent un bloc sont : `erb`, `haml`,
`liquid`, `slim ` et `wlang`. La méthode générale `render` accepte elle aussi
un bloc.
### 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, MonMerveilleuxMoteurDeRendu
helpers do
def monmoteur(*args) render(:monmoteur, *args) end
end
get '/' do
monmoteur :index
end
```
Utilisera `./views/index.monmoteur`. Voir [le projet 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 méthodes
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
Les sessions sont utilisées 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 '/:valeur' 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'utilisez **pas**
`enable :sessions` dans ce cas mais chargez 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 '/:valeur' 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'
```
Pour que les différents sous-domaines de foo.com puissent partager une session,
vous devez précéder le domaine d'un *.* (point) :
```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 en-tê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 souhaitez 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 en-tê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 en-tê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 pour `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 connexion. 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](https://w3c.github.io/eventsource/) et peut servir de
base pour des [WebSockets](https://en.wikipedia.org/wiki/WebSocket). Vous
pouvez aussi l'employer pour augmenter le débit quand une partie du contenu
provient d'une ressource 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. 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
l'exécution du bloc passé au helper `stream`. 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
# interrogation prolongée
set :server, :thin
connexions = []
get '/souscrire' do
# abonne un client aux évènements du serveur
stream(:keep_open) do |out|
connexions << out
# purge les connexions abandonnées
connexions.reject!(&:closed?)
end
end
post '/message' do
connexions.each do |out|
# prévient le client qu'un nouveau message est arrivé
out << params['message'] << "\n"
# indique au client de se connecter à nouveau
out.close
end
# compte-rendu
"message reçu"
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 sera utilisé comme argument pour la méthode
`halt` :
```ruby
redirect to('/bar'), 303
redirect 'http://www.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éfinissez correctement vos en-têtes à la base pour un bon cache HTTP.
Vous pouvez facilement définir l'en-tê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'en-tête correspondant,
`Cache-Control` sera alors défini automatiquement :
```ruby
before do
expires 500, :public, :must_revalidate
end
```
Pour utiliser correctement le cache, 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](https://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 le cache de votre navigateur. Si vous êtes à la
recherche d'une solution rapide 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 ressource demandée existe déjà ou pas. Sinatra considère que les
requêtes portant sur des ressources sûres (tel que get) ou idempotentes (tel que
put) existent déjà et pour les autres ressources (par exemple dans le cas
de requêtes post) qu'il s'agit de nouvelles ressources. Vous pouvez modifier ce
comportement en passant une option `:new_resource` :
```ruby
get '/create' do
etag '', :new_resource => true
Article.create
erb :nouvel_article
end
```
Si vous souhaitez avoir un ETag faible, utilisez l'option `:kind` :
```ruby
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’en-tê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 valeurs possibles étant : nil
(par défaut), :attachment et :inline
- length
- en-tê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' # vrai
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? # vrai (méthodes similaires pour les autres
# verbes HTTP)
request.form_data? # faux
request["UN_ENTETE"] # valeur de l'en-tête UN_ENTETE
request.referrer # 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? # faux
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 utile. 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(vues, nom, moteur, &bloc)
_, dossier = vues.detect { |k,v| moteur == Tilt[k] }
dossier ||= vues[:default]
super(dossier, nom, moteur, &bloc)
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
de problème de performance dans le sens où `render` va utiliser `break` dès
qu'un fichier sera trouvé. De plus, l'emplacement des templates (et leur
contenu) est mis en cache si vous n'êtes pas en mode développement. Vous
devez 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, 'valeur'
# définir plusieurs paramètres
set :a => 1, :b => 2
# équivalent à "set :option, true"
enable :option
# équivalent à "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 APP_ENV) est `:production` :
```ruby
configure :production do
...
end
```
Lancé si l'environnement est `:production` ou `:test` :
```ruby
configure :production, :test do
...
end
```
Vous pouvez accéder à ces paramètres via `settings` :
```ruby
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/sinatra/sinatra/tree/master/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]
```
Par défaut, il faut que `:sessions` soit activé pour que Sinatra mette en place
un système de protection au niveau de la session. Dans le cas où vous gérez
vous même les sessions, vous devez utiliser l'option `:session` pour que cela
soit le cas :
```ruby
use Rack::Session::Pool
set :protection, :session => true
```
### 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_charset
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_charset >> "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['APP_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.
x_cascade
Indique s'il faut ou non définir le header X-Cascade lorsqu'aucune route
ne correspond. Défini à true par défaut.
## Environements
Il existe trois environnements prédéfinis : `"development"`,
`"production"` et `"test"`. Les environements peuvent être
sélectionné via la variable d'environnement `APP_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, définissez la
variable d'environnement `APP_ENV` :
``` shell
APP_ENV=production ruby my_app.rb
```
Vous pouvez utiliser une des méthodes `development?`, `test?` et `production?`
pour déterminer quel est l'environnement en cours :
```ruby
get '/' do
if settings.development?
"développement !"
else
"pas en développement !"
end
end
```
## 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'].message
end
```
Erreur sur mesure :
```ruby
error MonErreurSurMesure do
'Oups ! Il est arrivé...' + env['sinatra.error'].message
end
```
Donc si cette erreur est soulevée :
```ruby
get '/' do
raise MonErreurSurMesure, 'quelque chose de mal'
end
```
La réponse sera :
```
Oups ! Il est arrivé... 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 de
`development`.
## Les Middlewares Rack
Sinatra fonctionne avec [Rack](http://rack.github.io/), 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" -- composants qui viennent 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 d'utiliser 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://www.rubydoc.info/github/rack/rack/master/Rack/Builder)
(le plus souvent utilisé dans un fichier `rackup`). Par exemple, la méthode
`use` accepte divers arguments ainsi que des blocs :
```ruby
use Rack::Auth::Basic do |identifiant, mot_de_passe|
identifiant == 'admin' && mot_de_passe == 'secret'
end
```
Rack est distribué avec de nombreux middlewares standards pour loguer, débuguer,
faire du routage URL, de l'authentification ou gérer des sessions. Sinatra gère
plusieurs de ces composants automatiquement via son système de configuration, ce
qui vous dispense de faire un `use` pour ces derniers.
Vous trouverez d'autres middlewares intéressants sur
[rack](https://github.com/rack/rack/tree/master/lib/rack),
[rack-contrib](https://github.com/rack/rack-contrib#readm),
ou en consultant le [wiki de Rack](https://github.com/rack/rack/wiki/List-of-Middleware).
## 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 'minitest/autorun'
require 'rack/test'
class MonTest < Minitest::Test
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', :nom => 'Frank'
assert_equal 'Salut Frank !', last_response.body
end
def test_avec_agent
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://www.sinatrarb.com/configuration.html)
pour plus d'informations sur les options et leur fonctionnement. Si vous
souhaitez un comportement plus proche de celui obtenu lorsque vous définissez
votre application au niveau supérieur (aussi connu sous le nom de style
Classique), vous pouvez créer une classe héritant de `Sinatra::Application`.
```ruby
require 'sinatra/base'
class MyApp < Sinatra::Application
get '/' do
'Bonjour le monde !'
end
end
```
### 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
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 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 |
Modulaire |
app_file |
fichier chargeant sinatra |
fichier héritant de Sinatra::Base |
fichier héritant de Sinatra::Application |
run |
$0 == app_file |
false |
false |
logging |
true |
false |
true |
method_override |
true |
false |
true |
inline_templates |
true |
false |
true |
static |
true |
File.exist?(public_folder) |
true |
### 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 :
```shell
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 :
```shell
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. Cet 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 pour 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
Une application Sinatra correspond à une sous-classe de `Sinatra::Base`. Il
s'agit de `Sinatra::Application` si vous utilisez le DSL de haut niveau
(`require 'sinatra'`). Sinon c'est la sous-classe que vous avez définie. Dans
le contexte de cette classe, vous avez accès aux méthodes telles que `get` ou
`before`, mais pas aux objets `request` ou `session` étant donné que toutes
les requêtes sont traitées par une seule classe d'application.
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 chaque requête traitée, une nouvelle instance de votre classe
d'application est créée et tous vos gestionnaires sont exécutés dans ce
contexte. Depuis celui-ci, vous pouvez accéder aux objets `request` et
`session` ou 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, patch, link et unlink
* les filtres before et after
* les méthodes utilitaires (définies au moyen de `helpers`)
* les vues et 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 de variables/états avec le contexte de classe
(comprenez : `self` n'est pas le même). Vous pouvez ajouter des délégations de
méthode 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`
Pour vous faire une idée, vous pouvez jeter un coup d'oeil au
[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 :
```shell
ruby mon_application.rb [-h] [-x] [-e ENVIRONNEMENT] [-p PORT] [-o HOTE] [-s SERVEUR]
```
Avec les options :
```
-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)
```
### Multi-threading
_Cette partie est basée sur [une réponse StackOverflow][so-answer] de Konstantin._
Sinatra n'impose pas de modèle de concurrence. Sinatra est thread-safe, vous pouvez
donc utiliser n'importe quel gestionnaire Rack, comme Thin, Puma ou WEBrick en mode
multi-threaded.
Cela signifie néanmoins qu'il vous faudra spécifier les paramètres correspondant au
gestionnaire Rack utilisé lors du démarrage du serveur.
L'exemple ci-dessous montre comment vous pouvez exécuter un serveur Thin de manière
multi-threaded:
```
# app.rb
require 'sinatra/base'
classe App < Sinatra::Base
get '/' do
'Bonjour le monde !'
end
end
App.run!
```
Pour démarrer le serveur, exécuter la commande suivante:
```
thin --threaded start
```
[so-answer]: http://stackoverflow.com/questions/6278817/is-sinatra-multi-threaded/6282999#6282999)
## 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 faire une mise à jour 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. Ruby 1.8.6 n’est plus supporté.
- Ruby 1.9.2
-
1.9.2 est totalement supporté. 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 Sinatra 1.5.
- 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. 1.9.3
sera supporté jusqu'à la sortie de Sinatra 2.0.
- Ruby 2.0.0
-
2.0.0 est totalement supporté et recommandé. L'abandon de son support
officiel n'est pas à l'ordre du jour.
- Rubinius
-
Rubinius est officiellement supporté (Rubinius >= 2.x). Un gem install
puma est recommandé.
- JRuby
-
La dernière version stable de JRuby est officiellement supportée. Il est
déconseillé d'utiliser des extensions C avec JRuby. Un gem install
trinidad est recommandé.
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 sur cette plateforme uniquement 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.1.0), mais nous ne pouvont rien garantir étant donné les évolutions
continuelles. La version 2.1.0 devrait être totalement supportée.
Sinatra devrait fonctionner sur n'importe quel système d'exploitation
supporté par l'implémentation Ruby choisie.
Si vous utilisez MacRuby, vous devriez `gem install control_tower`.
Il n'est pas possible d'utiliser Sinatra sur Cardinal, SmallRuby, BlueRuby ou
toute version de Ruby antérieure à 1.8.7 à l'heure actuelle.
## Essuyer les plâtres
Si vous souhaitez tester la toute dernière version de Sinatra, n'hésitez pas
à faire tourner votre application sur la branche master, celle-ci devrait être
stable.
Pour cela, la méthode la plus simple est d'installer une gem de prerelease que
nous publions de temps en temps :
```shell
gem install sinatra --pre
```
Ce qui permet de bénéficier des toutes dernières fonctionnalités.
### Installer avec Bundler
Il est cependant conseillé de passer par [Bundler](http://bundler.io) pour
faire tourner votre application avec la dernière version de Sinatra.
Pour commencer, installez bundler si nécessaire :
```shell
gem install bundler
```
Ensuite, créez un fichier `Gemfile` dans le dossier de votre projet :
```ruby
source 'https://rubygems.org'
gem 'sinatra', :github => "sinatra/sinatra"
# autres dépendances
gem 'haml' # si par exemple vous utilisez haml
gem 'activerecord', '~> 3.0' # au cas où vous auriez besoin de ActiveRecord 3.x
```
Notez que vous devez lister toutes les dépendances de votre application dans
ce fichier `Gemfile`. Les dépendances directes de Sinatra (Rack et Tilt) seront
automatiquement téléchargées et ajoutées par Bundler.
Vous pouvez alors lancer votre application de la façon suivante :
```shell
bundle exec ruby myapp.rb
```
### Faire un clone local
Si vous ne souhaitez pas employer Bundler, vous pouvez cloner Sinatra en local
dans votre projet et démarrez votre application avec le dossier `sinatra/lib`
dans le `$LOAD_PATH` :
```shell
cd myapp
git clone git://github.com/sinatra/sinatra.git
ruby -I sinatra/lib myapp.rb
```
Et de temps en temps, vous devrez récupérer la dernière version du code source
de Sinatra :
```shell
cd myapp/sinatra
git pull
```
### Installer globalement
Une dernière méthode consiste à construire la gem vous-même :
```shell
git clone git://github.com/sinatra/sinatra.git
cd sinatra
rake sinatra.gemspec
rake install
```
Si vous installez les gems en tant que root, vous devez encore faire un :
```shell
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](https://github.com/sinatra/sinatra/issues)
* [Twitter](https://twitter.com/sinatra)
* [Mailing List](http://groups.google.com/group/sinatrarb/topics)
* IRC : [#sinatra](irc://chat.freenode.net/#sinatra) sur http://freenode.net
* [Sinatra Book](https://github.com/sinatra/sinatra-book/) 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://www.rubydoc.info/gems/sinatra)
ou du [HEAD courant](http://www.rubydoc.info/github/sinatra/sinatra) sur
http://www.rubydoc.info/
* [CI server](https://travis-ci.org/sinatra/sinatra)
sinatra-2.0.8.1/README.hu.md 0000664 0000000 0000000 00000045530 13603175240 0015223 0 ustar 00root root 0000000 0000000 # Sinatra
*Fontos megjegyzés: Ez a dokumentum csak egy fordítása az angol nyelvű
változatnak, és lehet, hogy nem naprakész.*
A Sinatra egy [DSL](https://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](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 /\/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 megjegyezni, 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 APP_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'].message
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.github.io/) -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://www.rubydoc.info/github/rack/rack/master/Rack/Builder) 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 < Minitest::Test
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_user_agent
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://www.sinatrarb.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)
## Több szálon futtatás
_Parafrázis [Konstantin StackOverflow válasza][so-answer] alapján_
A Sinatra nem szabja meg az konkurenciakezelés módját, hanem az alatta működő
Rack kezelőre (szerverre) hagyja ezt a feladatot, ami például a Thin, a Puma,
vagy a WEBrick. A Sinatra önmagában szálbiztos, tehát semmilyen probléma sem
adódik, ha a Rack kezelő többszálú konkurenciamodellt használ. Ezek szerint
szerverindításkor meg kell adni a Rack szervernek megfelelő indítási módot.
A következő példa egy többszálú Thin szerver indítását mutatja be.
```ruby
# app.rb
require 'sinatra/base'
class App < Sinatra::Base
get '/' do
"Hello, World"
end
end
App.run!
```
A szerverindítás parancsa a következő lenne:
``` shell
thin --threaded start
```
[so-answer]: http://stackoverflow.com/a/6282999/1725341
## 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://www.sinatrarb.com/) - Kiegészítő dokumentáció,
hírek, hasznos linkek
* [Közreműködés](http://www.sinatrarb.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](https://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-2.0.8.1/README.ja.md 0000664 0000000 0000000 00000275467 13603175240 0015217 0 ustar 00root root 0000000 0000000 # Sinatra
*注)
本文書は英語から翻訳したものであり、その内容が最新でない場合もあります。最新の情報はオリジナルの英語版を参照してください。*
Sinatraは最小の労力でRubyによるWebアプリケーションを手早く作るための[DSL](https://ja.wikipedia.org/wiki/メインページドメイン固有言語)です。
```ruby
# myapp.rb
require 'sinatra'
get '/' do
'Hello world!'
end
```
gemをインストールし、
```shell
gem install sinatra
```
次のように実行します。
```shell
ruby myapp.rb
```
[http://localhost:4567](http://localhost:4567) を開きます。
ThinがあればSinatraはこれを利用するので、`gem install thin`することをお薦めします。
## 目次
* [Sinatra](#sinatra)
* [目次](#目次)
* [ルーティング(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)
* [利用可能なテンプレート言語](#利用可能なテンプレート言語)
* [Haml テンプレート](#haml-テンプレート)
* [Erb テンプレート](#erb-テンプレート)
* [Builder テンプレート](#builder-テンプレート)
* [Nokogiri テンプレート](#nokogiri-テンプレート)
* [Sass テンプレート](#sass-テンプレート)
* [SCSS テンプレート](#scss-テンプレート)
* [Less テンプレート](#less-テンプレート)
* [Liquid テンプレート](#liquid-テンプレート)
* [Markdown テンプレート](#markdown-テンプレート)
* [Textile テンプレート](#textile-テンプレート)
* [RDoc テンプレート](#rdoc-テンプレート)
* [AsciiDoc テンプレート](#asciidoc-テンプレート)
* [Radius テンプレート](#radius-テンプレート)
* [Markaby テンプレート](#markaby-テンプレート)
* [RABL テンプレート](#rabl-テンプレート)
* [Slim テンプレート](#slim-テンプレート)
* [Creole テンプレート](#creole-テンプレート)
* [MediaWiki テンプレート](#mediawiki-テンプレート)
* [CoffeeScript テンプレート](#coffeescript-テンプレート)
* [Stylus テンプレート](#stylus-テンプレート)
* [Yajl テンプレート](#yajl-テンプレート)
* [WLang テンプレート](#wlang-テンプレート)
* [テンプレート内での変数へのアクセス](#テンプレート内での変数へのアクセス)
* [`yield`を伴うテンプレートとネストしたレイアウト](#yieldを伴うテンプレートとネストしたレイアウト)
* [インラインテンプレート(Inline Templates)](#インラインテンプレートinline-templates)
* [名前付きテンプレート(Named Templates)](#名前付きテンプレートnamed-templates)
* [ファイル拡張子の関連付け](#ファイル拡張子の関連付け)
* [オリジナルテンプレートエンジンの追加](#オリジナルテンプレートエンジンの追加)
* [カスタムロジックを使用したテンプレートの探索](#カスタムロジックを使用したテンプレートの探索)
* [フィルタ(Filters)](#フィルタfilters)
* [ヘルパー(Helpers)](#ヘルパーhelpers)
* [セッションの使用](#セッションの使用)
* [セッションミドルウェアの選択](#セッションミドルウェアの選択)
* [停止(Halting)](#停止halting)
* [パッシング(Passing)](#パッシングpassing)
* [別ルーティングの誘発](#別ルーティングの誘発)
* [ボディ、ステータスコードおよびヘッダの設定](#ボディステータスコードおよびヘッダの設定)
* [ストリーミングレスポンス(Streaming Responses)](#ストリーミングレスポンスstreaming-responses)
* [ロギング(Logging)](#ロギングlogging)
* [MIMEタイプ(Mime Types)](#mimeタイプmime-types)
* [URLの生成](#urlの生成)
* [ブラウザリダイレクト(Browser Redirect)](#ブラウザリダイレクトbrowser-redirect)
* [キャッシュ制御(Cache Control)](#キャッシュ制御cache-control)
* [ファイルの送信](#ファイルの送信)
* [リクエストオブジェクトへのアクセス](#リクエストオブジェクトへのアクセス)
* [アタッチメント(Attachments)](#アタッチメントattachments)
* [日付と時刻の取り扱い](#日付と時刻の取り扱い)
* [テンプレートファイルの探索](#テンプレートファイルの探索)
* [コンフィギュレーション(Configuration)](#コンフィギュレーションconfiguration)
* [攻撃防御に対する設定](#攻撃防御に対する設定)
* [利用可能な設定](#利用可能な設定)
* [環境設定(Environments)](#環境設定environments)
* [エラーハンドリング(Error Handling)](#エラーハンドリングerror-handling)
* [Not Found](#not-found)
* [エラー(Error)](#エラーerror)
* [Rackミドルウェア(Rack Middleware)](#rackミドルウェアrack-middleware)
* [テスト(Testing)](#テストtesting)
* [Sinatra::Base - ミドルウェア、ライブラリおよびモジュラーアプリ](#sinatrabase---ミドルウェアライブラリおよびモジュラーアプリ)
* [モジュラースタイル vs クラッシックスタイル](#モジュラースタイル-vs-クラッシックスタイル)
* [モジュラーアプリケーションの提供](#モジュラーアプリケーションの提供)
* [config.ruを用いたクラッシックスタイルアプリケーションの使用](#configruを用いたクラッシックスタイルアプリケーションの使用)
* [config.ruはいつ使うのか?](#configruはいつ使うのか)
* [Sinatraのミドルウェアとしての利用](#sinatraのミドルウェアとしての利用)
* [動的なアプリケーションの生成](#動的なアプリケーションの生成)
* [スコープとバインディング(Scopes and Binding)](#スコープとバインディングscopes-and-binding)
* [アプリケーション/クラスのスコープ](#アプリケーションクラスのスコープ)
* [リクエスト/インスタンスのスコープ](#リクエストインスタンスのスコープ)
* [デリゲートスコープ](#デリゲートスコープ)
* [コマンドライン](#コマンドライン)
* [マルチスレッド](#マルチスレッド)
* [必要環境](#必要環境)
* [最新開発版](#最新開発版)
* [Bundlerを使う場合](#bundlerを使う場合)
* [直接組み込む場合](#直接組み込む場合)
* [グローバル環境にインストールする場合](#グローバル環境にインストールする場合)
* [バージョニング(Versioning)](#バージョニングversioning)
* [参考文献](#参考文献)
## ルーティング(Routes)
Sinatraでは、ルーティングはHTTPメソッドとURLマッチングパターンがペアになっています。
ルーティングはブロックに結び付けられています。
```ruby
get '/' do
.. 何か見せる ..
end
post '/' do
.. 何か生成する ..
end
put '/' do
.. 何か更新する ..
end
patch '/' do
.. 何か修正する ..
end
delete '/' do
.. 何か削除する ..
end
options '/' do
.. 何か満たす ..
end
link '/' do
.. 何かリンクを張る ..
end
unlink '/' do
.. 何かアンリンクする ..
end
```
ルーティングは定義された順番にマッチします。
リクエストに最初にマッチしたルーティングが呼び出されます。
トレイリングスラッシュを付けたルートは、そうでないルートと異なったものになります。
```ruby
get '/foo' do
# Does not match "GET /foo/"
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|
# "GET /hello/foo" と "GET /hello/bar" にマッチ
# params['name'] は 'foo' か 'bar'
# n が params['name'] を保持
"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 '/download/*.*' do |path, ext|
[path, ext] # => ["path/to/file", "xml"]
end
```
ルーティングを正規表現にマッチさせることもできます。
```ruby
get /\/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
```
ところで、ディレクトリトラバーサル攻撃防御設定を無効にしないと(下記参照)、
ルーティングにマッチする前にリクエストパスが修正される可能性があります。
## 条件(Conditions)
ルーティングにはユーザエージェントのようなさまざまな条件を含めることができます。
```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エリアです。アクセスを拒否します!"
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
```
複数の値を取る条件には、アスタリスクを使います。
```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ステータスコードのいずれかとして妥当なオブジェクトであればどのようなオブジェクトでも返すことができます。
* 3つの要素を含む配列:
`[ステータス(Integer), ヘッダ(Hash), レスポンスボディ(#eachに応答する)]`
* 2つの要素を含む配列:
`[ステータス(Integer), レスポンスボディ(#eachに応答する)]`
* `#each`に応答するオブジェクト。通常はそのまま何も返さないが、
与えられたブロックに文字列を渡す。
* ステータスコードを表現する整数(Integer)
これにより、例えばストリーミングを簡単に実装することができます。
```ruby
class Stream
def each
100.times { |i| yield "#{i}\n" }
end
end
get('/') { Stream.new }
```
後述する`stream`ヘルパーメソッドを使って、定型パターンを減らしつつストリーミングロジックをルーティングに埋め込むこともできます。
## カスタムルーティングマッチャー(Custom Route Matchers)
先述のように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
```
## 静的ファイル(Static Files)
静的ファイルは`./public`ディレクトリから配信されます。
`:public_folder`オプションを指定することで別の場所を指定することができます。
```ruby
set :public_folder, File.dirname(__FILE__) + '/static'
```
ノート: この静的ファイル用のディレクトリ名は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
```
テンプレートのレイアウトは第2引数のハッシュ形式のオプションをもとに表示されます。
```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
```
レンダリングメソッドに渡されたオプションは`set`で設定されたオプションを上書きします。
利用可能なオプション:
- locals
-
ドキュメントに渡されるローカルのリスト。パーシャルに便利。
例: 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
- layout_options
-
レイアウトをレンダリングするときだけに使う特別なオプション。例:
set :rdoc, :layout_options => { :views => 'views/layouts' }
テンプレートは`./views`ディレクトリ下に配置されています。
他のディレクトリを使用する場合の例:
```ruby
set :views, settings.root + '/templates'
```
テンプレートの参照は、テンプレートがサブディレクトリ内にある場合でも常にシンボルで指定することを覚えておいてください。
(これは`:'subdir/template'`または`'subdir/template'.to_sym`のように指定することを意味します。)
レンダリングメソッドにシンボルではなく文字列を渡してしまうと、そのまま文字列として出力してしまいます。
### リテラルテンプレート(Literal Templates)
```ruby
get '/' do
haml '%div.title Hello World'
end
```
これはテンプレート文字列をレンダリングしています。
テンプレート文字列に関連するファイルパスや行数を`:path`や`:line`オプションで指定することで、バックトレースを明確にすることができます。
```ruby
get '/' do
haml '%div.title Hello World', :path => 'examples/file.haml', :line => 3
end
```
### 利用可能なテンプレート言語
いくつかの言語には複数の実装があります。使用する(そしてスレッドセーフにする)実装を指定するには、それを最初にrequireしてください。
```ruby
require 'rdiscount' # または require 'bluecloth'
get('/') { markdown :index }
```
#### Haml テンプレート
依存 |
haml |
ファイル拡張子 |
.haml |
例 |
haml :index, :format => :html5 |
#### Erb テンプレート
依存 |
erubi
または erubis
または erb (Rubyに同梱)
|
ファイル拡張子 |
.erb, .rhtml または .erubi (Erubiだけ) または.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`を除く)を呼び出すことができないため、ほぼ全ての場合にlocalsを指定する必要があるでしょう。
#### Markdown テンプレート
Markdownからメソッドを呼び出すことも、localsに変数を渡すこともできません。
それゆえ、他のレンダリングエンジンとの組み合わせで使うのが普通です。
```ruby
erb :overview, :locals => { :text => markdown(:introduction) }
```
ノート: 他のテンプレート内で`markdown`メソッドを呼び出せます。
```ruby
%h1 Hello From Haml!
%p= markdown(:greetings)
```
MarkdownからはRubyを呼ぶことができないので、Markdownで書かれたレイアウトを使うことはできません。しかしながら、`:layout_engine`オプションを渡すことでテンプレートのものとは異なるレンダリングエンジンをレイアウトのために使うことができます。
#### Textile テンプレート
依存 |
RedCloth |
ファイル拡張子 |
.textile |
例 |
textile :index, :layout_engine => :erb |
Textileからメソッドを呼び出すことも、localsに変数を渡すこともできません。
それゆえ、他のレンダリングエンジンとの組み合わせで使うのが普通です。
```ruby
erb :overview, :locals => { :text => textile(:introduction) }
```
ノート: 他のテンプレート内で`textile`メソッドを呼び出せます。
```ruby
%h1 Hello From Haml!
%p= textile(:greetings)
```
TexttileからはRubyを呼ぶことができないので、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からはRubyを呼ぶことができないので、RDocで書かれたレイアウトを使うことはできません。しかしながら、`:layout_engine`オプションを渡すことでテンプレートのものとは異なるレンダリングエンジンをレイアウトのために使うことができます。
#### AsciiDoc テンプレート
依存 |
Asciidoctor |
ファイル拡張子 |
.asciidoc, .adoc and .ad |
例 |
asciidoc :README, :layout_engine => :erb |
AsciiDocテンプレートからRubyのメソッドを直接呼び出すことができないため、ほぼ全ての場合にlocalsを指定する必要があるでしょう。
#### Radius テンプレート
依存 |
Radius |
ファイル拡張子 |
.radius |
例 |
radius :index, :locals => { :key => 'value' } |
RadiusテンプレートからRubyのメソッドを直接呼び出すことができないため、ほぼ全ての場合にlocalsを指定する必要があるでしょう。
#### Markaby テンプレート
依存 |
Markaby |
ファイル拡張子 |
.mab |
例 |
markaby { h1 "Welcome!" } |
インラインテンプレート用にブロックを取ることもできます(例を参照)。
#### RABL テンプレート
依存 |
Rabl |
ファイル拡張子 |
.rabl |
例 |
rabl :index |
#### Slim テンプレート
#### 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からはRubyを呼ぶことができないので、Creoleで書かれたレイアウトを使うことはできません。しかしながら、`:layout_engine`オプションを渡すことでテンプレートのものとは異なるレンダリングエンジンをレイアウトのために使うことができます。
#### MediaWiki テンプレート
依存 |
WikiCloth |
ファイル拡張子 |
.mediawiki および .mw |
例 |
mediawiki :wiki, :layout_engine => :erb |
MediaWikiのテンプレートは直接メソッドから呼び出したり、ローカル変数を通すことはできません。それゆえに、通常は別のレンダリングエンジンと組み合わせて利用します。
```ruby
erb :overview, :locals => { :text => mediawiki(:introduction) }
```
ノート: 他のテンプレートから部分的に`mediawiki`メソッドを呼び出すことも可能です。
#### CoffeeScript テンプレート
#### Stylus テンプレート
Stylusテンプレートを使えるようにする前に、まず`stylus`と`stylus/tilt`を読み込む必要があります。
```ruby
require 'sinatra'
require 'stylus'
require 'stylus/tilt'
get '/' do
stylus :example
end
```
#### 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メソッドの呼び出しは一般的ではないので、ほとんどの場合にlocalsを指定する必要があるでしょう。しかしながら、WLangで書かれたレイアウトは`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`を伴うテンプレートとネストしたレイアウト
レイアウトは通常、`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`メソッドもブロックを取れます。
### インラインテンプレート(Inline Templates)
テンプレートはソースファイルの最後で定義することもできます。
```ruby
require 'sinatra'
get '/' do
haml :index
end
__END__
@@ layout
%html
= yield
@@ index
%div.title Hello world!!!!!
```
ノート: Sinatraをrequireするソースファイル内で定義されたインラインテンプレートは自動的に読み込まれます。他のソースファイル内にインラインテンプレートがある場合には`enable :inline_templates`を明示的に呼んでください。
### 名前付きテンプレート(Named 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`を使います。例えば、Textileテンプレートに`tt`というファイル拡張子を使いたい場合は、以下のようにします。
```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 を参照してください。
### カスタムロジックを使用したテンプレートの探索
オリジナルテンプレートの検索メカニズムを実装するためには、`#find_template`メソッドを実装します。
```ruby
configure do
set :views [ './views/a', './views/b' ]
end
def find_template(views, name, engine, &block)
Array(views).each do |v|
super(v, name, engine, &block)
end
end
```
## フィルタ(Filters)
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フィルタでまだ利用できず、その後に生成されることになります。
フィルタにはオプションとしてパターンを渡すことができ、この場合はリクエストのパスがパターンにマッチした場合にのみフィルタが評価されるようになります。
```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
```
その効果は、アプリケーションクラスにモジュールをインクルードするのと同じです。
### セッションの使用
セッションはリクエスト間での状態維持のために使用されます。セッションを有効化すると、ユーザセッションごとに一つのセッションハッシュが与えられます。
```ruby
enable :sessions
get '/' do
"value = " << session[:value].inspect
end
get '/:value' do
session[:value] = params['value']
end
```
ノート: `enable :sessions`は実際にはすべてのデータをクッキーに保持します。これは必ずしも期待通りのものにならないかもしれません(例えば、大量のデータを保持することでトラフィックが増大するなど)。Rackセッションミドルウェアの利用が可能であり、その場合は`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
```
セキュリティ向上のため、クッキー内のセッションデータはセッション秘密鍵(session secret)で署名されます。Sinatraによりランダムな秘密鍵が個別に生成されます。しかし、この秘密鍵はアプリケーションの立ち上げごとに変わってしまうので、すべてのアプリケーションのインスタンスで共有できる秘密鍵をセットしたくなるかもしれません。
```ruby
set :session_secret, 'super secret'
```
更に、設定変更をしたい場合は、`sessions`の設定においてオプションハッシュを保持することもできます。
```ruby
set :sessions, :domain => 'foo.com'
```
foo.comのサブドメイン上のアプリ間でセッションを共有化したいときは、代わりにドメインの前に *.* を付けます。
```ruby
set :sessions, :domain => '.foo.com'
```
#### セッションミドルウェアの選択
`enable :sessions`とすることで、クッキー内の全てのデータを実際に保存してしまうことに注意してください。
これは、あなたが望む挙動ではない(例えば、大量のデータを保存することでトラフィックが増大してしまう)かもしれません。
あなたは、次のいずれかの方法によって、任意のRackセッションミドルウェアを使用することができます。
```ruby
enable :sessions
set :session_store, Rack::Session::Pool
```
オプションのハッシュを設定するためには、次のようにします。
```ruby
set :sessions, :expire_after => 2592000
set :session_store, Rack::Session::Pool
```
他の方法は`enable :sessions`を**しない**で、他のミドルウェアの選択と同様にあなた自身でミドルウェアを選択することです。
この方法を選択する場合は、セッションベースの保護は**デフォルトで有効にならない**ということに注意することが重要です。
これを満たすためのRackミドルウェアを追加することが必要になります。
```ruby
use Rack::Session::Pool, :expire_after => 2592000
use Rack::Protection::RemoteToken
use Rack::Protection::SessionHijacking
```
より詳しい情報は、「攻撃防御に対する設定」の項を参照してください。
### 停止(Halting)
フィルタまたはルーティング内で直ちにリクエストを止める場合
```ruby
halt
```
この際、ステータスを指定することもできます。
```ruby
halt 410
```
body部を指定することも、
```ruby
halt 'ここにbodyを書く'
```
ステータスとbody部を指定することも、
```ruby
halt 401, '立ち去れ!'
```
ヘッダを付けることもできます。
```ruby
halt 402, {'Content-Type' => 'text/plain'}, 'リベンジ'
```
もちろん、テンプレートを`halt`に結びつけることも可能です。
```ruby
halt erb(:error)
```
### パッシング(Passing)
ルーティングは`pass`を使って次のルーティングに飛ばすことができます。
```ruby
get '/guess/:who' do
pass unless params['who'] == 'Frank'
"見つかっちゃった!"
end
get '/guess/*' do
"はずれです!"
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の仕様を参照してください。
### ボディ、ステータスコードおよびヘッダの設定
ステータスコードおよびレスポンスボディを、ルーティングブロックの戻り値にセットすることが可能であり、これは推奨されています。しかし、あるケースでは実行フローの任意のタイミングでボディをセットしたくなるかもしれません。`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; https://www.ietf.org/rfc/rfc2324.txt"
body "I'm a tea pot!"
end
```
引数を伴わない`body`、`headers`、`status`などは、それらの現在の値にアクセスするために使えます。
### ストリーミングレスポンス(Streaming Responses)
レスポンスボディの部分を未だ生成している段階で、データを送り出したいということがあります。極端な例では、クライアントがコネクションを閉じるまでデータを送り続けたいことがあります。`stream`ヘルパーを使えば、独自ラッパーを作る必要はありません。
```ruby
get '/' do
stream do |out|
out << "それは伝 -\n"
sleep 0.5
out << " (少し待つ) \n"
sleep 1
out << "- 説になる!\n"
end
end
```
これはストリーミングAPI、[Server Sent Events](https://w3c.github.io/eventsource/)の実装を可能にし、[WebSockets](https://en.wikipedia.org/wiki/WebSocket)の土台に使うことができます。また、一部のコンテンツが遅いリソースに依存しているときに、スループットを上げるために使うこともできます。
ノート: ストリーミングの挙動、特に並行リクエスト(cuncurrent requests)の数は、アプリケーションを提供するのに使われるWebサーバに強く依存します。いくつかのサーバは、ストリーミングを全くサポートしません。サーバがストリーミングをサポートしない場合、ボディは`stream`に渡されたブロックの実行が終了した後、一度に全部送られることになります。ストリーミングは、Shotgunを使った場合は全く動作しません。
オプション引数が`keep_open`にセットされている場合、ストリームオブジェクト上で`close`は呼ばれず、実行フローの任意の遅れたタイミングでユーザがこれを閉じることを可能にします。これはThinやRainbowsのようなイベント型サーバ上でしか機能しません。他のサーバでは依然ストリームは閉じられます。
```ruby
# ロングポーリング
set :server, :thin
connections = []
get '/subscribe' do
# サーバイベントにおけるクライアントの関心を登録
stream(:keep_open) do |out|
connections << out
# 死んでいるコネクションを排除
connections.reject!(&:closed?)
end
end
post '/message' do
connections.each do |out|
# クライアントへ新規メッセージ到着の通知
out << params['message'] << "\n"
# クライアントへの再接続の指示
out.close
end
# 肯定応答
"message received"
end
```
クライアントはソケットに書き込もうとしている接続を閉じることも可能です。そのため、記述しようとする前に`out.closed?`をチェックすることを勧めます。
### ロギング(Logging)
リクエストスコープにおいて、`logger`ヘルパーは`Logger`インスタンスを作り出します。
```ruby
get '/' do
logger.info "loading data"
# ...
end
```
このロガーは、自動でRackハンドラのロギング設定を参照します。ロギングが無効(disabled)にされている場合、このメソッドはダミーオブジェクトを返すので、ルーティングやフィルタにおいて特に心配することはありません。
ノート: ロギングは、`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タイプ(Mime Types)
`send_file`か静的ファイルを使う時、SinatraがMIMEタイプを理解できない場合があります。その時は `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ルーティングを、それらがあれば考慮に入れます。
このメソッドには`to`というエイリアスがあります(以下の例を参照)。
### ブラウザリダイレクト(Browser Redirect)
`redirect` ヘルパーメソッドを使うことで、ブラウザをリダイレクトさせることができます。
```ruby
get '/foo' do
redirect to('/bar')
end
```
他に追加されるパラメータは、`halt`に渡される引数と同様に取り扱われます。
```ruby
redirect to('/bar'), 303
redirect 'https://www.google.com/', 'wrong place, buddy'
```
また、`redirect back`を使えば、簡単にユーザが来たページへ戻るリダイレクトを作れます。
```ruby
get '/foo' do
"do something"
end
get '/bar' do
do_something
redirect back
end
```
redirectに引数を渡すには、それをクエリーに追加するか、
```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 header)は、次のように簡単に設定できます。
```ruby
get '/' do
cache_control :public
"キャッシュしました!"
end
```
ヒント: キャッシングをbeforeフィルタ内で設定します。
```ruby
before do
cache_control :public, :must_revalidate, :max_age => 60
end
```
`expires`ヘルパーを対応するヘッダに使っている場合は、キャッシュ制御は自動で設定されます。
```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](https://ja.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`設定(以下を参照)を、キャッシュ制御ヘッダ情報を静的ファイルに追加するために使ってください。
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プロセスからのストリーミング以外の手段が使われる。このヘルパーメソッドを使うと、Sinatraは自動で範囲リクエスト(range requests)を扱う。
### リクエストオブジェクトへのアクセス
受信するリクエストオブジェクトは、`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_param"] # some_param変数の値。[]はパラメータハッシュのショートカット
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によって渡された生のenvハッシュ
end
```
`script_name`や`path_info`などのオプションは次のように利用することもできます。
```ruby
before { request.path_info = "/" }
get "/" do
"全てのリクエストはここに来る"
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
"保存しました!"
end
```
ファイル名を渡すこともできます。
```ruby
get '/' do
attachment "info.txt"
"保存しました!"
end
```
### 日付と時刻の取り扱い
Sinatraは`time_for`ヘルパーメソッドを提供しており、それは与えられた値からTimeオブジェクトを生成します。これはまた`DateTime`、`Date`および類似のクラスを変換できます。
```ruby
get '/' do
pass if Time.now > time_for('Dec 23, 2012')
"まだ時間がある"
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
```
この例はあまり有益ではありません。しかし、このメソッドを、独自の探索機構で働くようオーバーライドするなら有益になります。例えば、複数のビューディレクトリを使えるようにしたいときがあります。
```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モードでの起動でない限りはキャッシュされます。このことは、複雑なメソッド(a really crazy method)を書いた場合は記憶しておく必要があります。
## コンフィギュレーション(Configuration)
どの環境でも起動時に1回だけ実行されます。
```ruby
configure do
# 1つのオプションをセット
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
```
環境設定(`APP_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/sinatra/sinatra/tree/master/rack-protection#readme)を使用することで、アプリケーションを一般的な日和見的攻撃から守っています。これは簡単に無効化できます(が、アプリケーションに大量の一般的な脆弱性を埋め込むことになってしまいます)。
```ruby
disable :protection
```
ある1つの防御を無効にするには、`protection`にハッシュでオプションを指定します。
```ruby
set :protection, :except => :path_traversal
```
配列を渡すことで、複数の防御を無効にすることもできます。
```ruby
set :protection, :except => [:path_traversal, :session_hijacking]
```
デフォルトでSinatraは、`:sessions`が有効になっている場合、セッションベースの防御だけを設定します。しかし、自身でセッションを設定したい場合があります。その場合は、`:session`オプションを渡すことにより、セッションベースの防御を設定することができます。
```ruby
use Rack::Session::Pool
set :protection, :session => true
```
### 利用可能な設定
- absolute_redirects
-
無効のとき、Sinatraは相対リダイレクトを許容するが、RFC 2616 (HTTP 1.1)は絶対リダイレクトのみを許容するので、これには準拠しなくなる。
-
アプリケーションが、適切に設定されていないリバースプロキシの裏で走っている場合は有効。ノート: urlヘルパーは、第2引数にfalseを渡さない限り、依然として絶対URLを生成する。
- デフォルトは無効。
- add_charset
-
Mimeタイプ content_typeヘルパーが自動的にキャラクタセット情報をここに追加する。このオプションは書き換えるのではなく、値を追加するようにすること。
settings.add_charset << "application/foobar"
- app_file
-
メインのアプリケーションファイルのパスであり、プロジェクトのルート、viewsおよびpublicフォルダを見つけるために使われる。
- bind
- バインドするIPアドレス(デフォルト: `environment`がdevelopmentにセットされているときは、0.0.0.0 または localhost)。ビルトインサーバでのみ使われる。
- default_encoding
- 不明なときに仮定されるエンコーディング(デフォルトは"utf-8")。
- dump_errors
- ログにおけるエラーの表示。
- environment
-
現在の環境。デフォルトはENV['APP_ENV']、それが無い場合は"development"。
- logging
- ロガーの使用。
- lock
-
各リクエスト周りのロックの配置で、Rubyプロセスごとにリクエスト処理を並行して走らせるようにする。
- アプリケーションがスレッドセーフでなければ有効。デフォルトは無効。
- method_override
-
put/deleteフォームを、それらをサポートしないブラウザで使えるように_methodのおまじないを使えるようにする。
- port
- 待ち受けポート。ビルトインサーバのみで有効。
- prefixed_redirects
-
絶対パスが与えられていないときに、リダイレクトにrequest.script_nameを挿入するか否かの設定。これによりredirect '/foo'は、redirect to('/foo')のように振る舞う。デフォルトは無効。
- protection
- Web攻撃防御を有効にするか否かの設定。上述の攻撃防御の項を参照。
- public_dir
- public_folderのエイリアス。以下を参照。
- public_folder
-
publicファイルが提供されるディレクトリのパス。静的ファイルの提供が有効になっている場合にのみ使われる (以下のstatic設定を参照)。設定されていない場合、app_file設定から推定。
- reload_templates
-
リクエスト間でテンプレートを再ロードするか否かの設定。developmentモードでは有効。
- root
-
プロジェクトのルートディレクトリのパス。設定されていない場合、app_file設定から推定。
- raise_errors
-
例外発生の設定(アプリケーションは止まる)。environmentが"test"に設定されているときはデフォルトは有効。それ以外は無効。
- run
-
有効のとき、SinatraがWebサーバの起動を取り扱う。rackupまたは他の手段を使うときは有効にしないこと。
- running
- ビルトインサーバが稼働中か?この設定を変更しないこと!
- server
-
ビルトインサーバとして使用するサーバまたはサーバ群の指定。指定順位は優先度を表し、デフォルトはRuby実装に依存。
- sessions
-
Rack::Session::Cookieを使ったクッキーベースのセッションサポートの有効化。詳しくは、'セッションの使用'の項を参照のこと。
- show_exceptions
-
例外発生時にブラウザにスタックトレースを表示する。environmentが"development"に設定されているときは、デフォルトで有効。それ以外は無効。
-
また、:after_handlerをセットすることができ、これにより、ブラウザにスタックトレースを表示する前に、アプリケーション固有のエラーハンドリングを起動させられる。
- static
- Sinatraが静的ファイルの提供を取り扱うかの設定。
- その取り扱いができるサーバを使う場合は無効。
- 無効化でパフォーマンスは改善する
-
クラッシックスタイルではデフォルトで有効。モジュラースタイルでは無効。
- static_cache_control
-
Sinatraが静的ファイルを提供するときこれをセットして、レスポンスにCache-Controlヘッダを追加するようにする。cache_controlヘルパーを使うこと。デフォルトは無効。
-
複数の値をセットするときは明示的に配列を使う:
set :static_cache_control, [:public, :max_age => 300]
- threaded
-
trueに設定されているときは、Thinにリクエストを処理するためにEventMachine.deferを使うことを通知する。
- views
-
ビューディレクトリのパス。設定されていない場合、app_file設定から推定する。
- x_cascade
-
マッチするルーティングが無い場合に、X-Cascadeヘッダをセットするか否かの設定。デフォルトはtrue。
## 環境設定(Environments)
3種類の既定環境、`"development"`、`"production"`および`"test"`があります。環境は、`APP_ENV`環境変数を通して設定できます。デフォルト値は、`"development"`です。`"development"`環境において、すべてのテンプレートは、各リクエスト間で再ロードされ、そして、特別の`not_found`および`error`ハンドラがブラウザにスタックトレースを表示します。`"production"`および`"test"`環境においては、テンプレートはデフォルトでキャッシュされます。
異なる環境を走らせるには、`APP_ENV`環境変数を設定します。
```shell
APP_ENV=production ruby my_app.rb
```
既定メソッド、`development?`、`test?`および`production?`を、現在の環境設定を確認するために使えます。
```ruby
get '/' do
if settings.development?
"development!"
else
"not development!"
end
end
```
## エラーハンドリング(Error Handling)
エラーハンドラはルーティングおよびbeforeフィルタと同じコンテキストで実行されます。すなわちこれは、`haml`、`erb`、`halt`といった便利なものが全て使えることを意味します。
### 未検出(Not Found)
`Sinatra::NotFound`例外が発生したとき、またはレスポンスのステータスコードが404のときに、`not_found`ハンドラが発動します。
```ruby
not_found do
'ファイルが存在しません'
end
```
### エラー(Error)
`error`ハンドラはルーティングブロックまたはフィルタ内で例外が発生したときはいつでも発動します。
しかし、環境設定がdevelopmentの場合は`:after_handler`を設定している場合のみ発動するようになります。
```ruby
set :show_exceptions, :after_handler
```
例外オブジェクトはRack変数`sinatra.error`から取得できます。
```ruby
error do
'エラーが発生しました。 - ' + env['sinatra.error'].message
end
```
エラーをカスタマイズする場合は、
```ruby
error MyCustomError do
'エラーメッセージ...' + env['sinatra.error'].message
end
```
と書いておいて、下記のように呼び出します。
```ruby
get '/' do
raise MyCustomError, '何かがまずかったようです'
end
```
そうするとこうなります。
```
エラーメッセージ... 何かがまずかったようです
```
あるいは、ステータスコードに対応するエラーハンドラを設定することもできます。
```ruby
error 403 do
'Access forbidden'
end
get '/secret' do
403
end
```
範囲指定もできます。
```ruby
error 400..510 do
'Boom'
end
```
Sinatraを開発環境の下で実行している場合は、特別な`not_found`および`error`ハンドラが導入され、これは親切なスタックトレースと追加のデバッギング情報をブラウザに表示します。
## Rackミドルウェア(Rack Middleware)
SinatraはRuby製Webフレームワークのミニマルな標準的インタフェースである[Rack](https://rack.github.io/)上に構築されています。アプリケーションデベロッパーにとってRackにおける最も興味深い機能は、「ミドルウェア(middleware)」をサポートしていることであり、これは、サーバとアプリケーションとの間に置かれ、HTTPリクエスト/レスポンスを監視および/または操作することで、各種の汎用的機能を提供するコンポーネントです。
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://www.rubydoc.info/github/rack/rack/master/Rack/Builder)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#readm)、
または[Rack wiki](https://github.com/rack/rack/wiki/List-of-Middleware)。
## テスト(Testing)
SinatraでのテストはRackベースのテストライブラリまたはフレームワークを使って書くことができます。[Rack::Test](http://www.rubydoc.info/github/brynary/rack-test/master/frames)をお薦めします。
```ruby
require 'my_sinatra_app'
require 'minitest/autorun'
require 'rack/test'
class MyAppTest < Minitest::Test
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_user_agent
get '/', {}, 'HTTP_USER_AGENT' => 'Songbird'
assert_equal "Songbirdを使ってます!", last_response.body
end
end
```
ノート: モジュラースタイルでSinatraを使う場合は、上記`Sinatra::Application`をアプリケーションのクラス名に置き換えてください。
## Sinatra::Base - ミドルウェア、ライブラリおよびモジュラーアプリ
軽量なアプリケーションであれば、トップレベルでアプリケーションを定義していくことはうまくいきますが、再利用性可能なコンポーネント、例えばRackミドルウェア、RailsのMetal、サーバコンポーネントを含むシンプルなライブラリ、あるいはSinatraの拡張プログラムを構築するような場合、これは無視できない欠点を持つものとなります。トップレベルは、軽量なアプリケーションのスタイルにおける設定(例えば、単一のアプリケーションファイル、`./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で利用できるものと全く同じです。ほとんどのトップレベルで記述されたアプリは、以下の2点を修正することで`Sinatra::Base`コンポーネントに変えることができます。
* `sinatra`の代わりに`sinatra/base`を読み込む
(そうしない場合、SinatraのDSLメソッドの全てがmainの名前空間にインポートされます)
* ルーティング、エラーハンドラ、フィルタ、オプションを`Sinatra::Base`のサブクラスに書く
`Sinatra::Base`はまっさらです。ビルトインサーバを含む、ほとんどのオプションがデフォルトで無効になっています。利用可能なオプションとその挙動の詳細については[Configuring Settings](http://www.sinatrarb.com/configuration.html)(英語)をご覧ください。
もしもクラシックスタイルと同じような挙動のアプリケーションをトップレベルで定義させる必要があれば、`Sinatra::Application`をサブクラス化させてください。
```ruby
require "sinatra/base"
class MyApp < Sinatra::Application
get "/" do
'Hello world!'
end
end
```
### モジュラースタイル vs クラッシックスタイル
一般的認識と違って、クラッシックスタイルを使うことに問題はなにもありません。それがそのアプリケーションに合っているのであれば、モジュラーアプリケーションに移行する必要はありません。
モジュラースタイルを使わずにクラッシックスタイルを使った場合の一番の不利な点は、Rubyプロセスごとにただ一つのSinatraアプリケーションしか持てない点です。複数が必要な場合はモジュラースタイルに移行してください。モジュラースタイルとクラッシックスタイルを混合できないということはありません。
一方のスタイルから他方へ移行する場合、デフォルト設定がわずかに異なる点に注意が必要です。
設定 |
クラッシック |
モジュラー |
モジュラー |
app_file |
sinatraを読み込むファイル |
Sinatra::Baseをサブクラス化したファイル |
Sinatra::Applicationをサブクラス化したファイル |
run |
$0 == app_file |
false |
false |
logging |
true |
false |
true |
method_override |
true |
false |
true |
inline_templates |
true |
false |
true |
static |
true |
File.exist?(public_folder) |
true |
### モジュラーアプリケーションの提供
モジュラーアプリケーションを開始、つまり`run!`を使って開始させる二種類のやり方があります。
```ruby
# my_app.rb
require 'sinatra/base'
class MyApp < Sinatra::Base
# ... アプリケーションのコードを書く ...
# Rubyファイルが直接実行されたらサーバを立ち上げる
run! if app_file == $0
end
```
として、次のように起動するか、
```shell
ruby my_app.rb
```
または、Rackハンドラを使えるようにする`config.ru`ファイルを書いて、
```ruby
# config.ru (rackupで起動)
require './my_app'
run MyApp
```
起動します。
```shell
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
# ミドルウェアはbeforeフィルタの前に実行される
use LoginScreen
before do
unless session['user_name']
halt "アクセスは拒否されました。ログインしてください。"
end
end
get('/') { "Hello #{session['user_name']}." }
end
```
### 動的なアプリケーションの生成
新しいアプリケーションを実行時に、定数に割り当てることなく生成したくなる場合があるでしょう。`Sinatra.new`を使えばそれができます。
```ruby
require 'sinatra/base'
my_app = Sinatra.new { get('/') { "hi" } }
my_app.run!
```
これは省略できる引数として、それが継承するアプリケーションを取ります。
```ruby
# config.ru (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
```
これは特にSinatraのextensionをテストするときや、Sinatraを自身のライブラリで利用する場合に役立ちます。
これはまた、Sinatraをミドルウェアとして利用することを極めて簡単にします。
```ruby
require 'sinatra/base'
use Sinatra do
get('/') { ... }
end
run RailsProject::Application
```
## スコープとバインディング(Scopes and 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`の値として使われるProcまたはブロック
* `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 (not the same request)
end
"ルーティングが定義された!"
end
end
```
次の場所ではリクエストスコープバインディングを持ちます。
* get/head/post/put/delete/options/patch/link/unlink ブロック
* before/after フィルタ
* helper メソッド
* テンプレート/ビュー
### デリゲートスコープ
デリゲートスコープは、単にクラススコープにメソッドを転送します。
しかしながら、クラスのバインディングを持っていないため、クラススコープと全く同じふるまいをするわけではありません。
委譲すると明示的に示されたメソッドのみが利用可能であり、またクラススコープと変数/状態を共有することはできません(注:
異なった`self`を持っています)。
`Sinatra::Delegator.delegate :method_name`を呼び出すことによってデリゲートするメソッドを明示的に追加することができます。
次の場所ではデリゲートスコープを持ちます。
* もし`require "sinatra"`しているならば、トップレベルバインディング
* `Sinatra::Delegator` mixinでextendされたオブジェクト
コードをご覧ください: ここでは [Sinatra::Delegator
mixin](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/base.rb#L1609-1633)は[mainオブジェクトにextendされています](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/main.rb#L28-30)。
## コマンドライン
Sinatraアプリケーションは直接実行できます。
```shell
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)
```
### マルチスレッド
_この[StackOverflow](https://stackoverflow.com/a/6282999/5245129)
のKonstantinによる回答を言い換えています。_
Sinatraでは同時実行モデルを負わせることはできませんが、根本的な部分であるThinやPuma、WebrickのようなRackハンドラ(サーバー)部分に委ねることができます。
Sinatra自身はスレッドセーフであり、もしRackハンドラが同時実行モデルのスレッドを使用していても問題はありません。
つまり、これはサーバーを起動させる時、特定のRackハンドラに対して正しい起動処理を特定することが出来ます。
この例はThinサーバーをマルチスレッドで起動する方法のデモです。
```ruby
# app.rb
require 'sinatra/base'
class App < Sinatra::Base
get '/' do
"Hello, World"
end
end
App.run!
```
サーバーを開始するコマンドです。
```
thin --threaded start
```
## 必要環境
次のRubyバージョンが公式にサポートされています。
- Ruby 1.8.7
-
1.8.7は完全にサポートされていますが、特にそれでなければならないという理由がないのであれば、アップグレードまたはJRubyまたはRubiniusへの移行を薦めます。1.8.7のサポートがSinatra 2.0の前に終わることはないでしょう。Ruby 1.8.6はサポート対象外です。
- Ruby 1.9.2
-
1.9.2は完全にサポートされています。1.9.2p0は、Sinatraを起動したときにセグメントフォルトを引き起こすことが分かっているので、使わないでください。公式なサポートは、少なくともSinatra 1.5のリリースまでは続きます。
- Ruby 1.9.3
-
1.9.3は完全にサポート、そして推奨されています。以前のバージョンからの1.9.3への移行は全セッションを無効にする点、覚えておいてください。
- Ruby 2.0.0
-
2.0.0は完全にサポート、そして推奨されています。現在、その公式サポートを終了する計画はありません。
- Rubinius
-
Rubiniusは公式にサポートされています(Rubinius >= 2.x)。
gem install pumaすることが推奨されています。
- JRuby
-
JRubyの最新安定版が公式にサポートされています。JRubyでC拡張を使うことは推奨されていません。
gem install trinidadすることが推奨されています。
開発チームは常に最新となるRubyバージョンに注視しています。
次のRuby実装は公式にはサポートされていませんが、Sinatraが起動すると報告されています。
* JRubyとRubiniusの古いバージョン
* Ruby Enterprise Edition
* MacRuby, Maglev, IronRuby
* Ruby 1.9.0と1.9.1 (これらの使用はお薦めしません)
公式サポートをしないという意味は、問題がそこだけで起こり、サポートされているプラットフォーム上では起きない場合に、開発チームはそれはこちら側の問題ではないとみなすということです。
開発チームはまた、ruby-head(最新となる2.1.0)に対しCIを実行していますが、それが一貫して動くようになるまで何も保証しません。2.1.0が完全にサポートされればその限りではありません。
Sinatraは、利用するRuby実装がサポートしているオペレーティングシステム上なら動作するはずです。
MacRubyを使う場合は、`gem install control_tower`してください。
Sinatraは現在、Cardinal、SmallRuby、BlueRubyまたは1.8.7以前のバージョンのRuby上では動作しません。
## 最新開発版
Sinatraの最新開発版のコードを使いたい場合は、マスターブランチに対してアプリケーションを走らせて構いません。ある程度安定しています。また、適宜プレリリース版gemをpushしているので、
```shell
gem install sinatra --pre
```
すれば、最新の機能のいくつかを利用できます。
### Bundlerを使う場合
最新のSinatraでアプリケーションを動作させたい場合には、[Bundler](https://bundler.io)を使うのがお薦めのやり方です。
まず、Bundlerがなければそれをインストールします。
```shell
gem install bundler
```
そして、プロジェクトのディレクトリで、`Gemfile`を作ります。
```ruby
source 'https://rubygems.org'
gem 'sinatra', :github => "sinatra/sinatra"
# 他の依存ライブラリ
gem 'haml' # Hamlを使う場合
gem 'activerecord', '~> 3.0' # ActiveRecord 3.xが必要かもしれません
```
ノート: `Gemfile`にアプリケーションの依存ライブラリのすべてを並べる必要があります。しかし、Sinatraが直接依存するもの(RackおよびTile)はBundlerによって自動的に取り込まれ、追加されます。
これで、以下のようにしてアプリケーションを起動することができます。
```shell
bundle exec ruby myapp.rb
```
### 直接組み込む場合
ローカルにクローンを作って、`sinatra/lib`ディレクトリを`$LOAD_PATH`に追加してアプリケーションを起動します。
```shell
cd myapp
git clone git://github.com/sinatra/sinatra.git
ruby -I sinatra/lib myapp.rb
```
追ってSinatraのソースを更新する方法。
```shell
cd myapp/sinatra
git pull
```
### グローバル環境にインストールする場合
Sinatraのgemを自身でビルドすることもできます。
```shell
git clone git://github.com/sinatra/sinatra.git
cd sinatra
rake sinatra.gemspec
rake install
```
gemをルートとしてインストールする場合は、最後のステップはこうなります。
```shell
sudo rake install
```
## バージョニング(Versioning)
Sinatraは、[Semantic Versioning](https://semver.org/)におけるSemVerおよびSemVerTagの両方に準拠しています。
## 参考文献
* [プロジェクトサイト](http://www.sinatrarb.com/) - ドキュメント、ニュース、他のリソースへのリンクがあります。
* [プロジェクトに参加(貢献)する](http://www.sinatrarb.com/contributing.html) - バグレポート パッチの送信、サポートなど
* [Issue tracker](https://github.com/sinatra/sinatra/issues)
* [Twitter](https://twitter.com/sinatra)
* [メーリングリスト](https://groups.google.com/group/sinatrarb/topics)
* http://freenode.net上のIRC: [#sinatra](irc://chat.freenode.net/#sinatra)
* [Sinatra Book](https://github.com/sinatra/sinatra-book/) クックブック、チュートリアル
* [Sinatra Recipes](http://recipes.sinatrarb.com/) コミュニティによるレシピ集
* http://www.rubydoc.info/上のAPIドキュメント: [最新版(latest release)用](http://www.rubydoc.info/gems/sinatra)または[現在のHEAD用](http://www.rubydoc.info/github/sinatra/sinatra)
* [CIサーバ](https://travis-ci.org/sinatra/sinatra)
* [Greenbear Laboratory Rack日本語マニュアル](http://route477.net/w/RackReferenceJa.html)
sinatra-2.0.8.1/README.ko.md 0000664 0000000 0000000 00000247525 13603175240 0015230 0 ustar 00root root 0000000 0000000 # Sinatra
*주의: 이 문서는 영문판의 번역본이며 최신판 문서와 다를 수 있습니다.*
Sinatra는 최소한의 노력으로 루비 기반 웹 애플리케이션을 신속하게 만들 수 있게
해 주는 [DSL](https://en.wikipedia.org/wiki/Domain-specific_language)입니다.
```ruby
# myapp.rb
require 'sinatra'
get '/' do
'Hello world!'
end
```
아래의 명령어로 젬을 설치합니다.
```shell
gem install sinatra
```
아래의 명령어로 실행합니다.
```shell
ruby myapp.rb
```
[http://localhost:4567](http://localhost:4567) 를 확인해 보세요.
`gem install thin`도 함께 실행하기를 권장합니다.
thin이 설치되어 있을 경우 Sinatra는 thin을 통해 실행합니다.
## 목차
* [Sinatra](#sinatra)
* [목차](#목차)
* [라우터(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 템플릿](#haml-템플릿)
* [Erb 템플릿](#erb-템플릿)
* [Builder 템플릿](#builder-템플릿)
* [Nokogiri 템플릿](#nokogiri-템플릿)
* [Sass 템플릿](#sass-템플릿)
* [SCSS 템플릿](#scss-템플릿)
* [Less 템플릿](#less-템플릿)
* [Liquid 템플릿](#liquid-템플릿)
* [Markdown 템플릿](#markdown-템플릿)
* [Textile 템플릿](#textile-템플릿)
* [RDoc 템플릿](#rdoc-템플릿)
* [AsciiDoc 템플릿](#asciidoc-템플릿)
* [Radius 템플릿](#radius-템플릿)
* [Markaby 템플릿](#markaby-템플릿)
* [RABL 템플릿](#rabl-템플릿)
* [Slim 템플릿](#slim-템플릿)
* [Creole 템플릿](#creole-템플릿)
* [MediaWiki 템플릿](#mediawiki-템플릿)
* [CoffeeScript 템플릿](#coffeescript-템플릿)
* [Stylus 템플릿](#stylus-템플릿)
* [Yajl 템플릿](#yajl-템플릿)
* [WLang 템플릿](#wlang-템플릿)
* [템플릿에서 변수에 접근하기](#템플릿에서-변수에-접근하기)
* [템플릿에서의 `yield` 와 중첩 레이아웃](#템플릿에서의-yield-와-중첩-레이아웃)
* [인라인 템플릿](#인라인-템플릿)
* [이름을 가지는 템플릿(Named Templates)](#이름을-가지는-템플릿named-templates)
* [파일 확장자 연결하기](#파일-확장자-연결하기)
* [나만의 고유한 템플릿 엔진 추가하기](#나만의-고유한-템플릿-엔진-추가하기)
* [템플릿 검사를 위한 커스텀 로직 사용하기](#템플릿-검사를-위한-커스텀-로직-사용하기)
* [필터(Filters)](#필터filters)
* [헬퍼(Helpers)](#헬퍼helpers)
* [세션(Sessions) 사용하기](#세션sessions-사용하기)
* [중단하기(Halting)](#중단하기halting)
* [넘기기(Passing)](#넘기기passing)
* [다른 라우터 부르기(Triggering Another Route)](#다른-라우터-부르기triggering-another-route)
* [본문, 상태 코드 및 헤더 설정하기](#본문-상태-코드-및-헤더-설정하기)
* [응답 스트리밍(Streaming Responses)](#응답-스트리밍streaming-responses)
* [로깅(Logging)](#로깅logging)
* [마임 타입(Mime Types)](#마임-타입mime-types)
* [URL 생성하기](#url-생성하기)
* [브라우저 재지정(Browser Redirect)](#브라우저-재지정browser-redirect)
* [캐시 컨트롤(Cache Control)](#캐시-컨트롤cache-control)
* [파일 전송하기(Sending Files)](#파일-전송하기sending-files)
* [요청 객체에 접근하기(Accessing the Request Object)](#요청-객체에-접근하기accessing-the-request-object)
* [첨부(Attachments)](#첨부attachments)
* [날짜와 시간 다루기](#날짜와-시간-다루기)
* [템플릿 파일 참조하기](#템플릿-파일-참조하기)
* [설정(Configuration)](#설정configuration)
* [공격 방어 설정하기(Configuring attack protection)](#공격-방어-설정하기configuring-attack-protection)
* [가능한 설정들(Available Settings)](#가능한-설정들available-settings)
* [환경(Environments)](#환경environments)
* [에러 처리(Error Handling)](#에러-처리error-handling)
* [찾을 수 없음(Not Found)](#찾을-수-없음not-found)
* [에러](#에러)
* [Rack 미들웨어(Rack Middleware)](#rack-미들웨어rack-middleware)
* [테스팅(Testing)](#테스팅testing)
* [Sinatra::Base - 미들웨어(Middleware), 라이브러리(Libraries), 그리고 모듈 앱(Modular Apps)](#sinatrabase---미들웨어middleware-라이브러리libraries-그리고-모듈-앱modular-apps)
* [모듈(Modular) vs. 전통적 방식(Classic Style)](#모듈modular-vs-전통적-방식classic-style)
* [모듈 애플리케이션(Modular Application) 제공하기](#모듈-애플리케이션modular-application-제공하기)
* [config.ru로 전통적 방식의 애플리케이션 사용하기](#configru로-전통적-방식의-애플리케이션-사용하기)
* [언제 config.ru를 사용할까?](#언제-configru를-사용할까)
* [Sinatra를 미들웨어로 사용하기](#sinatra를-미들웨어로-사용하기)
* [동적인 애플리케이션 생성(Dynamic Application Creation)](#동적인-애플리케이션-생성dynamic-application-creation)
* [범위(Scopes)와 바인딩(Binding)](#범위scopes와-바인딩binding)
* [애플리케이션/클래스 범위](#애플리케이션클래스-범위)
* [요청/인스턴스 범위](#요청인스턴스-범위)
* [위임 범위(Delegation Scope)](#위임-범위delegation-scope)
* [명령행(Command Line)](#명령행command-line)
* [다중 스레드(Multi-threading)](#다중-스레드multi-threading)
* [요구사항(Requirement)](#요구사항requirement)
* [최신(The Bleeding Edge)](#최신the-bleeding-edge)
* [Bundler를 사용하여](#bundler를-사용하여)
* [직접 하기(Roll Your Own)](#직접-하기roll-your-own)
* [전역으로 설치(Install Globally)](#전역으로-설치install-globally)
* [버저닝(Versioning)](#버저닝versioning)
* [더 읽을 거리(Further Reading)](#더-읽을-거리further-reading)
## 라우터(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
link '/' do
.. 무언가 관계맺기(affiliate) ..
end
unlink '/' do
.. 무언가 격리하기(separate) ..
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|
# "GET /hello/foo" 및 "GET /hello/bar"와 매치
# params['name']은 'foo' 또는 'bar'
# n 에는 params['name']가 저장
"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 /\/hello\/([\w]+)/ do
"Hello, #{params['captures'].first}!"
end
```
블록 매개변수로도 사용가능합니다.
```ruby
get %r{/hello/([\w]+)} do |c|
# "GET /meta/hello/world", "GET /hello/world/1234" 등과 매치
"Hello, #{c}!"
end
```
라우터 패턴에는 선택적인(optional) 매개변수도 올 수 있습니다.
```ruby
get '/posts/:format?' do
# "GET /posts/" 는 물론 "GET /posts/json", "GET /posts/xml" 와 같은 어떤 확장자와도 매칭
end
```
쿼리 파라메터로도 이용가능 합니다.
```ruby
get '/posts' do
# matches "GET /posts?title=foo&author=bar"
title = params['title']
author = params['author']
# uses title and author variables; query is optional to the /posts route
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
```
`provides`는 request의 허용된 해더를 검색합니다.
사용자 정의 조건도 쉽게 만들 수 있습니다.
```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 상태 코드가 되는 어떠한 객체라도 반환할 수 있습니다.
* 세 요소를 가진 배열: `[상태 (Integer), 헤더 (Hash), 응답 본문 (#each에 반응)]`
* 두 요소를 가진 배열: `[상태 (Integer), 응답 본문 (#each에 반응)]`
* `#each`에 반응하고 주어진 블록으로 문자열만을 전달하는 객체
* 상태 코드를 의미하는 Integer
이것을 이용한 예를 들자면, 스트리밍(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`파일이 존재할 경우 기본값은 `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'`을
사용)에도 예외는 없습니다. 반드시 심볼이어야 하는 이유는, 문자열을 넘기면
렌더링 메서드가 전달된 문자열을 직접 렌더하기 때문입니다.
### 리터럴 템플릿(Literal Templates)
```ruby
get '/' do
haml '%div.title Hello World'
end
```
템플릿 문자열을 렌더합니다.
### 가능한 템플릿 언어들(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 |
예제 |
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 템플릿
Markdown에서는 메서드 호출 뿐 아니라 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` 옵션으로 레이아웃의 템플릿을
다른 렌더링 엔진으로 렌더링 할 수는 있습니다.
#### AsciiDoc 템플릿
의존성 |
Asciidoctor |
파일 확장자 |
.asciidoc, .adoc and .ad |
예제 |
asciidoc :README, :layout_engine => :erb |
AsciiDoc 템플릿에서는 루비 메서드를 호출할 수 없기
때문에, 거의 대부분의 경우 locals를 전달해야 합니다.
#### 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` 옵션으로 레이아웃의 템플릿을
다른 렌더링 엔진으로 렌더링 할 수는 있습니다.
#### MediaWiki 템플릿
의존성 |
WikiCloth |
파일 확장자 |
.mediawiki and .mw |
예제 |
mediawiki :wiki, :layout_engine => :erb |
MediaWiki 마크업에서는 메서드 호출 뿐 아니라 locals 전달도 불가능합니다.
따라서 일반적으로는 다른 렌더링 엔진과 함께 사용하게 됩니다.
```ruby
erb :overview, :locals => { :text => mediawiki(:introduction) }
```
다른 템플릿 속에서 `mediawiki` 메서드를 호출할 수도 있습니다.
```ruby
%h1 Hello From Haml!
%p= mediawiki(:greetings)
```
MediaWiki에서 루비를 호출할 수 없기 때문에, MediaWiki으로 작성된 레이아웃은
사용할 수 없습니다. 하지만, `:layout_engine` 옵션으로 레이아웃의 템플릿을
다른 렌더링 엔진으로 렌더링 할 수는 있습니다.
#### CoffeeScript 템플릿
#### Stylus 템플릿
Stylus 템플릿을 사용가능하게 하려면, 먼저 `stylus`와 `stylus/tilt`를 로드
해야합니다.
```ruby
require 'sinatra'
require 'stylus'
require 'stylus/tilt'
get '/' do
stylus :example
end
```
#### Yajl 템플릿
의존성 |
yajl-ruby |
파일 확장자 |
.yajl |
예제 |
yajl :index,
:locals => { :key => 'qux' },
:callback => 'present',
:variable => 'resource'
|
템플릿 소스는 루비 문자열로 평가(evaluate)되고, 결과인 json 변수는 `#to_json`으로 변환됩니다.
```ruby
json = { :foo => 'bar' }
json[:baz] = key
```
`:callback`과 `:variable` 옵션은 렌더된 객체를 꾸미는데(decorate) 사용할 수 있습니다.
```javascript
var resource = {"foo":"bar","baz":"qux"};
present(resource);
```
#### WLang 템플릿
의존성 |
WLang |
파일 확장자 |
.wlang |
예제 |
wlang :index, :locals => { :key => 'value' } |
WLang 템플릿에서는 루비 메서드를 사용하는게 일반적이지 않기
때문에, 거의 대부분의 경우 locals를 전달합니다. 그래도
WLang으로 쓰여진 레이아웃과 `yield`는 지원합니다.
### 템플릿에서 변수에 접근하기
템플릿은 라우터 핸들러와 같은 맥락(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
```
이 방법은 주로 템플릿을 다른 템플릿 속에서 파셜(partial)로 렌더링할
때 사용됩니다.
### 템플릿에서의 `yield` 와 중첩 레이아웃
레이아웃은 보통 `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를 require한 소스 파일에 정의된 인라인 템플릿은 자동으로
로드됩니다. 다른 소스 파일에서 인라인 템플릿을 사용하려면 명시적으로
`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 참조하세요.
### 템플릿 검사를 위한 커스텀 로직 사용하기
고유한 템플릿 룩업을 구현하기 위해서는 `#find_template` 메서드를 만드셔야 합니다.
```ruby
configure do
set :views [ './views/a', './views/b' ]
end
def find_template(views, name, engine, &block)
Array(views).each do |v|
super(v, name, engine, &block)
end
end
```
## 필터(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) 사용하기
세션은 요청 동안에 상태를 유지하기 위해 사용합니다.
세션이 활성화되면, 사용자 세션 당 세션 해시 하나씩을 갖게 됩니다.
```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'
```
세션을 다른 foo.com의 서브도메인 들과 공유하기 원한다면, 다음에 나오는
것 처럼 도메인 앞에 *.*을 붙이셔야 합니다.
```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](https://w3c.github.io/eventsource/)를 구현할 수 있고, 이 방법은
[WebSockets](https://en.wikipedia.org/wiki/WebSocket)을 위한 기반으로 사용됩니다.
이 방법은 일부 콘텐츠가 느린 자원에 의존하는 경우에 스로풋(throughtput)을
높이기 위해 사용되기도 합니다.
스트리밍 동작, 특히 동시 요청의 수는 애플리케이션을 서빙하는 웹서버에 크게
의존합니다. 일부의 경우 아예 스트리밍을 지원하지 조차 않습니다. 만약 서버가
스트리밍을 지원하지 않는다면, 본문은 `stream` 으로 전달된 블록이 수행을 마친
후에 한꺼번에 반환됩니다. 이런 한번에 쏘는 샷건같은 방식으로는 스트리밍은
움직이지 않습니다.
선택적 매개변수 `keep_open`이 설정되어 있다면, 스트림 객체에서 `close`를
호출하지 않을 것이고, 나중에 실행 흐름 상의 어느 시점에서 스트림을 닫을 수
있습니다. 이 옵션은 Thin과 Rainbow 같은 이벤트 기반 서버에서만 작동하고
다른 서버들은 여전히 스트림을 닫습니다.
```ruby
# long polling
set :server, :thin
connections = []
get '/subscribe' do
# register a client's interest in server events
stream(:keep_open) do |out|
connections << out
# purge dead connections
connections.reject!(&:closed?)
end
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)
요청 스코프(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 라우터가 있다면 참고합니다.
이 메서드는 `to`라는 별칭으로도 사용할 수 있습니다. (아래 예제 참조)
### 브라우저 재지정(Browser Redirect)
`redirect` 헬퍼 메서드를 사용하여 브라우저를 리다이렉트 시킬 수 있습니다.
```ruby
get '/foo' do
redirect to('/bar')
end
```
다른 부가적인 매개변수들은 `halt`에 전달하는 인자들과 비슷합니다.
```ruby
redirect to('/bar'), 303
redirect 'http://www.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](https://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)
응답(response)으로 파일의 컨탠츠를 리턴하려면, `send_file` 헬퍼 메서드를 사용하면 됩니다.
```ruby
get '/' do
send_file 'foo.png'
end
```
이 메서드는 몇 가지 옵션을 받습니다.
```ruby
send_file 'foo.png', :type => :jpg
```
옵션들:
- filename
- 응답에서 사용되는 파일명. 기본값은 실제 파일명.
- last_modified
- Last-Modified 헤더값. 기본값은 파일의 mtime.
- type
- Content-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
```
이것만으로는 그렇게 유용하지는 않습니다만, 이 메서드를 오버라이드하여 여러분만의
참조 메커니즘에서 가로채게 하면 유용해집니다. 예를 들어, 하나 이상의 뷰 디렉터리를
사용하고자 한다면 이렇게 하세요.
```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
```
환경(APP_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/sinatra/sinatra/tree/master/rack-protection#readme)을 사용하여
일반적이고 일어날 수 있는 공격에 대비합니다. 이 모듈은 간단하게 비활성시킬 수 있습니다.
(하지만 애플리케이션에 엄청나게 많은 취약성을 야기합니다.)
```ruby
disable :protection
```
하나의 방어층만 스킵하려면, 옵션 해시에 `protection`을 설정하면 됩니다.
```ruby
set :protection, :except => :path_traversal
```
배열로 넘김으로써 방어층 여러 개를 비활성화할 수 있습니다.
```ruby
set :protection, :except => [:path_traversal, :session_hijacking]
```
기본적으로 `:sessions`가 활성 중일 때만 Sinatra는 방어층을 설정합니다.
때로는 자신만의 세션을 설정할 때도 있습니다. 이런 경우 `:session` 옵션을
넘겨줌으로써 세션을 기반으로한 방어층을 설정 할 수 있습니다.
```ruby
use Rack::Session::Pool
set :protection, :session => true
```
### 가능한 설정들(Available Settings)
- absolute_redirects
-
만약 비활성이면, Sinatra는 상대경로 리다이렉트를 허용할 것이지만,
이렇게 되면 Sinatra는 더 이상 오직 절대경로 리다이렉트만 허용하고 있는
RFC 2616(HTTP 1.1)에 위배됨.
-
적정하게 설정되지 않은 리버스 프록시 하에서 앱을 실행 중이라면 활성화시킬 것.
rul 헬퍼는, 만약 두 번째 매개변수로 false를 전달하지만 않는다면,
여전히 절대경로 URL을 생성할 것임에 유의.
- 기본값은 비활성.
- add_charset
-
content_type가 문자셋 정보에 자동으로 추가하게 될 마임(mime) 타입.
이 옵션은 오버라이딩하지 말고 추가해야 함.
settings.add_charset << "application/foobar"
- app_file
-
메인 애플리케이션 파일의 경로. 프로젝트 루트, 뷰, public 폴더,
인라인 템플릿을 파악할 때 사용됨.
- bind
- 바인드할 IP 주소(기본값: 0.0.0.0 이나
`environment`가 개발로 설정 되어있으면 localhost). 오직
빌트인(built-in) 서버에서만 사용됨.
- default_encoding
- 인코딩을 알 수 없을 때 인코딩(기본값은 "utf-8").
- dump_errors
- 로그안의 에러 출력.
- environment
-
현재 환경, 기본값은 ENV['APP_ENV'] ENV에 없을 경우엔 "development".
- logging
- 로거(logger) 사용.
- lock
-
Ruby 프로세스 당 요청을 동시에 할 경우에만 매 요청에 걸쳐 잠금(lock)을 설정.
- 앱이 스레드에 안전(thread-safe)하지 않으면 활성화시킬 것. 기본값은 비활성.
- method_override
-
put/delete를 지원하지 않는 브라우저에서 put/delete 폼을 허용하는
_method 꼼수 사용.
- port
- 접속 포트. 빌트인 서버에서만 사용됨.
- prefixed_redirects
-
절대경로가 주어지지 않은 리다이렉트에 request.script_name를
삽입할지 여부를 결정. 활성화 하면 redirect '/foo'는
redirect to('/foo')처럼 동작. 기본값은 비활성.
- protection
- 웹 공격 방어를 활성화시킬 건지 여부. 위의 보안 섹션 참조.
- public_dir
- public_folder의 별칭. 밑을 참조.
- public_folder
-
public 파일이 제공될 폴더의 경로.
static 파일 제공이 활성화된 경우만 사용됨(아래 static참조).
만약 설정이 없으면 app_file로부터 유추됨.
- reload_templates
-
요청 간에 템플릿을 리로드(reload)할 건지 여부. 개발 모드에서는 활성됨.
- root
-
프로젝트 루트 디렉터리 경로. 설정이 없으면 app_file 설정으로부터 유추됨.
- raise_errors
-
예외 발생(애플리케이션은 중단됨).
기본값은 environment가 "test"인 경우는 활성, 그렇지 않으면 비활성.
- run
-
활성화되면, Sinatra가 웹서버의 시작을 핸들링.
rackup 또는 다른 도구를 사용하는 경우라면 활성화시키지 말 것.
- running
- 빌트인 서버가 실행 중인가? 이 설정은 변경하지 말 것!
- server
-
빌트인 서버로 사용할 서버 또는 서버 목록.
기본값은 루비구현에 따라 다르며 순서는 우선순위를 의미.
- sessions
-
Rack::Session::Cookie를 사용한 쿠키 기반 세션 활성화.
보다 자세한 정보는 '세션 사용하기' 참조.
- show_exceptions
-
예외 발생 시에 브라우저에 스택 추적을 보임.
기본값은 environment가 "development"인
경우는 활성, 나머지는 비활성.
-
:after_handler를 설정함으로써 브라우저에서
스택 트레이스를 보여주기 전에 앱에 특화된 에러 핸들링을
할 수도 있음.
- 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로부터 유추됨.
- x_cascade
-
라우트를 찾지못했을 때의 X-Cascade 해더를 설정여부.
기본값은 true
## 환경(Environments)
3가지의 미리 정의된 `environments` `"development"`, `"production"`, `"test"`
가 있습니다. 환경은 `APP_ENV` 환경 변수를 통해서도 설정됩니다. 기본값은
`"development"`입니다. `"development"` 모드에서는 모든 템플릿들은 요청 간에
리로드됩니다. 또, `"development"` 모드에서는 특별한 `not_found` 와 `error`
핸들러가 브라우저에서 스택 트레이스를 볼 수 있게합니다.
`"production"`과 `"test"`에서는 기본적으로 템플릿은 캐시됩니다.
다른 환경으로 실행시키려면 `APP_ENV` 환경 변수를 사용하세요.
```shell
APP_ENV=production ruby my_app.rb
```
현재 설정된 환경이 무엇인지 검사하기 위해서는 준비된 `development?`, `test?`,
`production?` 메서드를 사용할 수 있습니다.
```ruby
get '/' do
if settings.development?
"development!"
else
"not development!"
end
end
```
## 에러 처리(Error Handling)
예외 핸들러는 라우터 및 사전 필터와 동일한 맥락에서 실행됩니다.
이 말인즉, `haml`, `erb`, `halt`같은 이들이 제공하는 모든 것들을 사용할 수
있다는 뜻입니다.
### 찾을 수 없음(Not Found)
`Sinatra::NotFound` 예외가 발생하거나 또는 응답의 상태 코드가 404라면,
`not_found` 핸들러가 호출됩니다.
```ruby
not_found do
'아무 곳에도 찾을 수 없습니다.'
end
```
### 에러
`error` 핸들러는 라우터 또는 필터에서 뭐든 오류가 발생할 경우에 호출됩니다.
하지만 개발 환경에서는 예외 확인 옵션을 `:after_handler`로 설정되어 있을 경우에만
실행됨을 주의하세요.
```ruby
set :show_exceptions, :after_handler
```
예외 객체는 Rack 변수 `sinatra.error`로부터 얻을 수 있습니다.
```ruby
error do
'고약한 오류가 발생했군요 - ' + env['sinatra.error'].message
end
```
사용자 정의 오류는 이렇게 정의합니다.
```ruby
error MyCustomError do
'무슨 일이 생겼나면요...' + env['sinatra.error'].message
end
```
그런 다음, 이 오류가 발생하면 이렇게 처리합니다.
```ruby
get '/' do
raise MyCustomError, '안좋은 일'
end
```
결과는 이렇습니다.
```
무슨 일이 생겼냐면요... 안좋은 일
```
상태 코드에 대해 오류 핸들러를 설치할 수도 있습니다.
```ruby
error 403 do
'액세스가 금지됨'
end
get '/secret' do
403
end
```
범위로 지정할 수도 있습니다.
```ruby
error 400..510 do
'어이쿠'
end
```
Sinatra는 개발 환경에서 동작할 때 브라우저에 괜찮은 스택 트레이스와 추가적인
디버그 정보를 보여주기 위해 특별한 `not_found` 와 `error` 핸들러를 설치합니다.
## Rack 미들웨어(Middleware)
Sinatra는 [Rack](http://rack.github.io/) 위에서 동작하며, Rack은 루비 웹
프레임워크를 위한 최소한의 표준 인터페이스입니다. Rack이 애플리케이션 개발자들에게
제공하는 가장 흥미로운 기능은 "미들웨어(middleware)"에 대한 지원입니다.
여기서 미들웨어란 서버와 여러분의 애플리케이션 사이에 위치하면서 HTTP 요청/응답을
모니터링하거나/조작함으로써 다양한 유형의 공통 기능을 제공하는 컴포넌트입니다.
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://www.rubydoc.info/github/rack/rack/master/Rack/Builder) 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),
[Rack wiki](https://github.com/rack/rack/wiki/List-of-Middleware)
에서 유용한 미들웨어들을 찾을 수 있습니다.
## 테스팅(Testing)
Sinatra 테스트는 많은 Rack 기반 테스팅 라이브러리, 프레임워크를 사용하여 작성가능합니다.
그 중 [Rack::Test](http://www.rubydoc.info/github/brynary/rack-test/master/frames)를 권장합니다.
```ruby
require 'my_sinatra_app'
require 'minitest/autorun'
require 'rack/test'
class MyAppTest < Minitest::Test
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_user_agent
get '/', {}, 'HTTP_USER_AGENT' => 'Songbird'
assert_equal "You're using Songbird!", last_response.body
end
end
```
주의: Sinatra를 모듈러 방식으로 사용한다면, `Sinatra::Application`
를 앱에서 사용하는 클래스 이름으로 바꾸세요.
## 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)입니다. 빌트인 서버를 비롯한 대부분의 옵션들이
기본값으로 꺼져 있습니다. 가능한 옵션들과 그 작동에 대한 상세는 [옵션과
설정](http://www.sinatrarb.com/configuration.html)을 참조하세요.
### 모듈(Modular) vs. 전통적 방식(Classic Style)
일반적인 믿음과는 반대로, 전통적 방식에 잘못된 부분은 없습니다. 여러분 애플리케이션에
맞다면, 모듈 애플리케이션으로 전환할 필요는 없습니다.
모듈 방식이 아닌 전통적 방식을 사용할 경우 생기는 주된 단점은 루비 프로세스 당
하나의 Sinatra 애플리케이션만 사용할 수 있다는 점입니다. 만약 하나 이상을 사용할
계획이라면 모듈 방식으로 전환하세요. 모듈 방식과 전통적 방식을 섞어쓰지 못할
이유는 없습니다.
방식을 전환할 경우에는, 기본값 설정의 미묘한 차이에 유의해야 합니다.
설정 |
전통적 방식 |
모듈 |
app_file |
sinatra를 로딩하는 파일 |
Sinatra::Base를 서브클래싱한 파일 |
run |
$0 == app_file |
false |
logging |
true |
false |
method_override |
true |
false |
inline_templates |
true |
false |
static |
true |
File.exist?(public_folder) |
### 모듈 애플리케이션(Modular Application) 제공하기
모듈 앱을 시작하는 두 가지 일반적인 옵션이 있습니다.
`run!`으로 능동적으로 시작하는 방법은 이렇습니다.
```ruby
# my_app.rb
require 'sinatra/base'
class MyApp < Sinatra::Base
# ... 여기에 앱 코드가 온다 ...
# 루비 파일이 직접 실행될 경우에 서버를 시작
run! if app_file == $0
end
```
이렇게 시작할 수도 있습니다.
```shell
ruby my_app.rb
```
`config.ru`와 함께 사용할수도 있습니다. 이 경우는 어떠한 Rack 핸들러도 사용할 수 있도록
허용 합다.
```ruby
# config.ru
require './my_app'
run MyApp
```
실행은 이렇게 합니다.
```shell
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를 최종점(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).
하지만 클래스 바인딩을 갖지 않기에 완전히 클래스 범위처럼 동작하지는 않습니다.
오직 명시적으로 위임(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 애플리케이션은 직접 실행할 수 있습니다.
```shell
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)
```
### 다중 스레드(Multi-threading)
_Konstantin의 [StackOverflow의 답변][so-answer]에서 가져왔습니다_
시나트라는 동시성 모델을 전혀 사용하지 않지만, Thin, Puma, WEBrick 같은
기저의 Rack 핸들러(서버)는 사용합니다. 시나트라 자신은 스레드에 안전하므로
랙 핸들러가 동시성 스레드 모델을 사용한다고해도 문제가 되지는 않습니다.
이는 서버를 시작할 때, 서버에 따른 정확한 호출 방법을 사용했을 때의
이야기입니다. 밑의 예제는 다중 스레드 Thin 서버를 시작하는 방법입니다.
```ruby
# app.rb
require 'sinatra/base'
class App < Sinatra::Base
get '/' do
"Hello, World"
end
end
App.run!
```
서버를 시작하는 명령어는 다음과 같습니다.
```shell
thin --threaded start
```
[so-answer]: http://stackoverflow.com/questions/6278817/is-sinatra-multi-threaded/6282999#6282999)
## 요구사항(Requirement)
다음의 루비 버전은 공식적으로 지원됩니다.
- Ruby 1.8.7
-
1.8.7은 완전하게 지원되지만, 꼭 그래야할 특별한 이유가 없다면,
1.9.2로 업그레이드하거나 또는 JRuby나 Rubinius로 전환할 것을 권장합니다.
1.8.7에 대한 지원은 Sinatra 2.0 이전에는 중단되지 않을 것입니다.
Ruby 1.8.6은 더이상 지원하지 않습니다.
- Ruby 1.9.2
-
1.9.2는 완전하게 지원됩니다. 1.9.2p0은, Sinatra를 실행했을 때 세그먼트 오류가
발생할수 있으므로 쓰지 마세요. 공식 지원은 Sinatra 1.5 이전에는 중단되지 않을
것입니다.
- Ruby 1.9.3
-
1.9.3은 완전하게 지원되고 권장합니다. 이전 버전에서 1.9.3으로 전환할 경우 모든
세션이 무효화되므로 주의하세요. 1.9.3에 대한 지원은 Sinatra 2.0 이전에는
중단되지 않을 것입니다.
- Ruby 2.x
-
2.x은 완전하게 지원되고 권장합니다. 현재 공식 지원 중지 계획은 없습니다.
- Rubinius
-
Rubinius는 공식적으로 지원됩니다. (Rubinius >= 2.x)
gem install puma를 권장합니다.
- JRuby
-
JRuby의 마지막 안정판은 공식적으로 지원됩니다. C 확장을 JRuby와 사용하는
것은 권장되지 않습니다.
gem install trinidad를 권장합니다.
새로 나오는 루비 버전도 주시하고 있습니다.
다음 루비 구현체들은 공식적으로 지원하지 않지만
여전히 Sinatra를 실행할 수 있는 것으로 알려져 있습니다.
* JRuby와 Rubinius 예전 버전
* Ruby Enterprise Edition
* MacRuby, Maglev, IronRuby
* Ruby 1.9.0 및 1.9.1 (이 버전들은 사용하지 말 것을 권합니다)
공식적으로 지원하지 않는다는 것의 의미는 무언가가 그 플랫폼에서만 잘못 동작하고,
지원되는 플랫폼에서는 정상적으로 동작할 경우, 우리의 문제가 아니라 그 플랫폼의 문제로
간주한다는 뜻입니다.
또한 우리는 CI를 ruby-head (MRI의 이후 릴리즈) 브랜치에 맞춰 실행하지만,
계속해서 변하고 있기 때문에 아무 것도 보장할 수는 없습니다.
앞으로 나올 2.x가 완전히 지원되길 기대합시다.
Sinatra는 선택한 루비 구현체가 지원하는 어떠한 운영체제에서도 작동해야
합니다.
MacRuby를 사용한다면, gem install control_tower 를 실행해 주세요.
현재 Cardinal, SmallRuby, BlueRuby 또는 1.8.7 이전의 루비 버전에서는
Sinatra를 실행할 수 없을 것입니다.
## 최신(The Bleeding Edge)
Sinatra의 가장 최근 코드를 사용하고자 한다면, 애플리케이션을 마스터 브랜치에 맞춰
실행하면 되므로 부담가지지 마세요. 하지만 덜 안정적일 것입니다.
주기적으로 사전배포(prerelease) 젬을 푸시하기 때문에, 최신 기능들을 얻기 위해
다음과 같이 할 수도 있습니다.
```shell
gem install sinatra --pre
```
### Bundler를 사용하여
여러분 애플리케이션을 최신 Sinatra로 실행하고자 한다면,
[Bundler](http://bundler.io)를 사용할 것을 권장합니다.
우선, 아직 설치하지 않았다면 bundler를 설치합니다.
```shell
gem install bundler
```
그런 다음, 프로젝트 디렉터리에서, `Gemfile`을 만듭니다.
```ruby
source 'https://rubygems.org'
gem 'sinatra', :github => "sinatra/sinatra"
# 다른 의존관계들
gem 'haml' # 예를 들어, haml을 사용한다면
gem 'activerecord', '~> 3.0' # 아마도 ActiveRecord 3.x도 필요할 것
```
`Gemfile`안에 애플리케이션의 모든 의존성을 적어야 합니다.
하지만, Sinatra가 직접적인 의존관계에 있는 것들(Rack과 Tilt)은
Bundler가 자동으로 찾아서 추가할 것입니다.
이제 앱을 실행할 수 있습니다.
```shell
bundle exec ruby myapp.rb
```
### 직접 하기(Roll Your Own)
로컬 클론(clone)을 생성한 다음 `$LOAD_PATH`에 `sinatra/lib` 디렉터리를 주고
여러분 앱을 실행합니다.
```shell
cd myapp
git clone git://github.com/sinatra/sinatra.git
ruby -I sinatra/lib myapp.rb
```
이후에 Sinatra 소스를 업데이트하려면 이렇게 하세요.
```shell
cd myapp/sinatra
git pull
```
### 전역으로 설치(Install Globally)
젬을 직접 빌드할 수 있습니다.
```shell
git clone git://github.com/sinatra/sinatra.git
cd sinatra
rake sinatra.gemspec
rake install
```
만약 젬을 루트로 설치한다면, 마지막 단계는 다음과 같이 해야 합니다.
```shell
sudo rake install
```
## 버저닝(Versioning)
Sinatra는 [시맨틱 버저닝Semantic Versioning](http://semver.org/)
[(번역)](http://surpreem.com/archives/380)의 SemVer,
SemVerTag를 준수합니다.
## 더 읽을 거리(Further Reading)
* [프로젝트 웹사이트](http://www.sinatrarb.com/) - 추가 문서들, 뉴스,
그리고 다른 리소스들에 대한 링크.
* [기여하기](http://www.sinatrarb.com/contributing) - 버그를 찾았나요?
도움이 필요한가요? 패치를 하셨나요?
* [이슈 트래커](https://github.com/sinatra/sinatra/issues)
* [트위터](https://twitter.com/sinatra)
* [메일링 리스트](http://groups.google.com/group/sinatrarb/topics)
* IRC: [#sinatra](irc://chat.freenode.net/#sinatra) http://freenode.net
* 슬랙의 [Sinatra & Friends](https://sinatrarb.slack.com)입니다.
[여기](https://sinatra-slack.herokuapp.com/)에서 가입가능합니다.
* [Sinatra Book](https://github.com/sinatra/sinatra-book/) Cookbook 튜토리얼
* [Sinatra Recipes](http://recipes.sinatrarb.com/) 커뮤니티가 만드는 레시피
* http://www.rubydoc.info/에 있는 [최종 릴리스](http://www.rubydoc.info/gems/sinatra)
또는 [current HEAD](http://www.rubydoc.info/github/sinatra/sinatra)에 대한 API 문서
* [CI server](https://travis-ci.org/sinatra/sinatra)
sinatra-2.0.8.1/README.malayalam.md 0000664 0000000 0000000 00000244375 13603175240 0016555 0 ustar 00root root 0000000 0000000 # സിനാട്ര
[](http://badge.fury.io/rb/sinatra)
[](https://travis-ci.org/sinatra/sinatra)
[](https://dependabot.com/compatibility-score.html?dependency-name=sinatra&package-manager=bundler&version-scheme=semver)
വെബ് അപ്പ്ലിക്കേഷൻസ് എളുപ്പത്തിൽ ഉണ്ടാക്കാനുള്ള ഒരു ലൈബ്രറി [DSL](https://en.wikipedia.org/wiki/Domain-specific_language) ആണ്.:
```റൂബി
# myapp.rb
require 'sinatra'
get '/' do
'Hello world!'
end
```
gem ഇൻസ്റ്റാൾ ചെയ്യുവാൻ:
```shell
gem install sinatra
```
റൺ ചെയ്യുവാൻ :
```shell / ഷെൽ
ruby myapp.rb
```
View at: [http://localhost:4567](http://localhost:4567)
സെർവർ വീണ്ടും സ്റ്റാർട്ട് ചെയ്യാതെ നിങ്ങളുടെ കോഡ് ചേഞ്ച് കാണാൻ സാധിക്കുകയില്ല
കോഡ് ചേഞ്ച് ചെയ്യുമ്പോൾ സെർവർ വീണ്ടും സ്റ്റാർട്ട് ചെയ്യാൻ മറക്കരുത്
[sinatra/reloader](http://www.sinatrarb.com/contrib/reloader).
എപ്പോഴും `gem install thin`,എന്ന് റൺ ചെയ്യുക , ഇത് ഏറ്റവും പുതിയ അപ്ലിക്കേഷൻ സെലക്ട് ചെയ്യാൻ നമ്മളെ സഹായിക്കും .
## ഉള്ളടക്കം
* [സിനാട്ര](#sinatra)
* [ഉള്ളടക്കം](#table-of-contents)
* [റൂട്സ്](#routes)
* [കണ്ടിഷൻസ്](#conditions)
* [റിട്ടേൺ വാല്യൂസ്](#return-values)
* [കസ്റ്റമ് റൂട്ട് മടീച്ചേഴ്സ് ](#custom-route-matchers)
* [സ്റ്റാറ്റിക് files](#static-files)
* [വ്യൂസ് / ടെംപ്ലേറ്റ്സ്](#views--templates)
* [ലിറ്ററൽ ടെംപ്ലേറ്റ്സ് ](#literal-templates)
* [ലഭ്യമായ ടെംപ്ലേറ്റ്സ് ഭാഷകൾ ](#available-template-languages)
* [Haml ടെംപ്ലേറ്റ്സ്](#haml-templates)
* [Erb ടെംപ്ലേറ്റ്സ്](#erb-templates)
* [Builder ടെംപ്ലേറ്റ്സ്](#builder-templates)
* [nokogiri ടെംപ്ലേറ്റ്സ്](#nokogiri-templates)
* [Sass ടെംപ്ലേറ്റ്സ്](#sass-templates)
* [SCSS ടെംപ്ലേറ്റ്സ്](#scss-templates)
* [Less ടെംപ്ലേറ്റ്സ്](#less-templates)
* [Liquid ടെംപ്ലേറ്റ്സ്](#liquid-templates)
* [Markdown ടെംപ്ലേറ്റ്സ്](#markdown-templates)
* [Textile ടെംപ്ലേറ്റ്സ്](#textile-templates)
* [RDoc ടെംപ്ലേറ്റ്സ്](#rdoc-templates)
* [AsciiDoc ടെംപ്ലേറ്റ്സ്](#asciidoc-templates)
* [Radius ടെംപ്ലേറ്റ്സ്](#radius-templates)
* [Markaby ടെംപ്ലേറ്റ്സ്](#markaby-templates)
* [RABL ടെംപ്ലേറ്റ്സ്](#rabl-templates)
* [Slim ടെംപ്ലേറ്റ്സ്](#slim-templates)
* [Creole ടെംപ്ലേറ്റ്സ്](#creole-templates)
* [MediaWiki ടെംപ്ലേറ്റ്സ്](#mediawiki-templates)
* [CoffeeScript ടെംപ്ലേറ്റ്സ്](#coffeescript-templates)
* [Stylus ടെംപ്ലേറ്റ്സ്](#stylus-templates)
* [Yajl ടെംപ്ലേറ്റ്സ്](#yajl-templates)
* [WLang ടെംപ്ലേറ്റ്സ്](#wlang-templates)
* [വാരിയബിൾസിനെ എടുക്കാൻ സഹായിക്കുന്ന ടെംപ്ലേറ്റ്സ്](#accessing-variables-in-templates)
* [Templates with `yield` and nested layouts](#templates-with-yield-and-nested-layouts)
* [Inline ടെംപ്ലേറ്റ്സ്](#inline-templates)
* [പേരുള്ള ടെംപ്ലേറ്റ്സ്](#named-templates)
* [Associating File Extensions](#associating-file-extensions)
* [നിങ്ങളുടെ സ്വന്തം ടെമ്പ്ലേറ്റ് എങ്ങിനെ ഉണ്ടാക്കാൻ സഹായിക്കുന്നു ](#adding-your-own-template-engine)
* [Using Custom Logic for Template Lookup](#using-custom-logic-for-template-lookup)
* [Filters](#filters)
* [Helpers](#helpers)
* [സെഷൻസ് ഉപയോഗിക്കുന്നു ](#using-sessions)
* [രഹസ്യമായി സെഷൻസ് സംരക്ഷിക്കുക ](#session-secret-security)
* [Session Config](#session-config)
* [സെഷൻ middlewate തിരഞ്ഞെടുക്കുക](#choosing-your-own-session-middleware)
* [ഹാൾട് ചെയ്യുക ](#halting)
* [Passing](#passing)
* [മറ്റൊരു റൂട്ട് ട്രിഗർ ചെയ്യുക ](#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)
* [ URLs Generating](#generating-urls)
* [Browser റീഡിറക്ട് ചെയ്യുക ](#browser-redirect)
* [Cache Control](#cache-control)
* [Sending Files](#sending-files)
* [Accessing the Request Object](#accessing-the-request-object)
* [അറ്റാച്മെന്റ്സ് ](#attachments)
* [ദിവസവും സമയവും ഡീൽ ചെയ്യക ](#dealing-with-date-and-time)
* [Template Files നോക്കുന്നു ](#looking-up-template-files)
* [Configuration](#configuration)
* [Configuring attack protection](#configuring-attack-protection)
* [Available Settings](#available-settings)
* [Environments](#environments)
* [ കൈകാര്യം ചെയ്യുക ](#error-handling)
* [കണ്ടെത്താൻ ആയില്ല ](#not-found)
* [തെറ്റ്](#error)
* [Rack Middleware](#rack-middleware)
* [ടെസ്റ്റ് ചെയ്യുക ](#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)
* [Multi-threading](#multi-threading)
* [ആവശ്യങ്ങൾ ](#requirement)
* [The Bleeding Edge](#the-bleeding-edge)
* [With Bundler](#with-bundler)
* [വേർഷൻ ചെയ്യുക ](#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:
```റൂബി
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
```
റൂട്സ് മാച്ച് ചെയ്യാനാ രീതിയില് ആണ് അത് നിർവചിക്കുന്നത് . ഏത് റിക്വസ്റ്റ് ആണോ റൂട്ട് ആയി ചേരുന്നത് ആ റൂട്ട് ആണ് വിളിക്കപെടുക .
ട്രെയ്ലറിങ് സ്ലാഷ്സ് ഉള്ള റൂട്സ് അത് ഇല്ലാത്തതിൽ നിന്ന് വ്യത്യാസം ഉള്ളത് ആണ് :
```ruby
get '/foo' do
# Does not match "GET /foo/"
end
```
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
```
```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
```
റൂട്ട് പാട്ടേഴ്സിൽ പേരുള്ള splat ഉണ്ടാകാറുണ്ട് അതിനെ '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
```
റെഗുലർ expressions :
```ruby
get /\/hello\/([\w]+)/ do
"Hello, #{params['captures'].first}!"
end
```
ബ്ലോക്ക് പരാമീറ്റർസ് ഉള്ള റൂട്ട് മാച്ചിങ് :
```ruby
get %r{/hello/([\w]+)} do |c|
# Matches "GET /meta/hello/world", "GET /hello/world/1234" etc.
"Hello, #{c}!"
end
```
```ruby
get '/posts/:format?' do
# matches "GET /posts/" and any extension "GET /posts/json", "GET /posts/xml" etc
end
```
റൂട്ട് പാറ്റെൺസ് ഇത് query പരാമീറ്റർസ് ഉണ്ടാകാം :
```ruby
get '/posts' do
# matches "GET /posts?title=foo&author=bar"
title = params['title']
author = params['author']
# uses title and author variables; query is optional to the /posts route
end
```
അതുപോലെ നിങ്ങൾ പാത ട്രവേഴ്സല് അറ്റാച്ച് പ്രൊട്ടക്ഷൻ (#configuring-attack-protection) ഡിസബിലെ ചെയ്തട്ടില്ലെങ്കിൽ റെക്സ് പാത മോഡിഫിയ ചെയ്യണത്തിനു മുൻപ് അത് മാച്ച് ചെയ്യപ്പെടും
You may customize the [Mustermann](https://github.com/sinatra/mustermann#readme)
options used for a given route by passing in a `:mustermann_opts` hash:
```ruby
get '\A/posts\z', :mustermann_opts => { :type => :regexp, :check_anchors => false } do
# matches /posts exactly, with explicit anchoring
"If you match an anchored pattern clap your hands!"
end
```
ഇത് [condition](#conditions), പോലെ തോന്നുമെങ്കിലും ഇ ഒപ്റേൻസ് global `:mustermann_opts` ആയി മെർജ് ചെയ്യപ്പെട്ടിരിക്കുന്നു
## കണ്ടിഷൻസ്
യൂസർ അഗെന്റ്റ് പോലുള്ള മാച്ചിങ് റൂട്സ് ഇത് അടങ്ങി ഇരിക്കുന്നു
```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
```
ഇതുപോലുള്ള വേറെ കണ്ടിഷൻസ് ആണ് 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
```
`provides ` ആക്സെപ്റ് ഹെൽഡർസ് നെ അന്വഷിക്കുന്നു
നിങ്ങളുടെ കണ്ടിഷൻസ് ഇനി എളുപ്പത്തിൽ ഉണ്ടാക്കാൻ സഹായിക്കുന്നു
```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
```
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
റൂട്ട് ബ്ലോക്കിന്റെ റിട്ടേൺ വാല്യൂ HTTP client യിലേക്ക് കടത്തിവിടുന്ന രേസ്പോൻസ് ബോഡിയെ തീരുമാനിക്കുന്നു. സാധാരണയായി ഇത് ഒരു സ്ട്രിംഗ് ആണ്. പക്ഷെ മറ്റു വാല്യൂകളെയും ഇത് സ്വീകരിക്കും
* മൂന്ന് എലെമെന്റ്സ് ഉള്ള അറേ : `[status (Integer), headers (Hash), response
body (responds to #each)]`
* രണ്ട് എലെമെന്റ്സ് ഉള്ള അറേ : `[status (Integer), response body (responds to
#each)]`
* An object that responds to `#each` and passes nothing but strings to
the given block
* Integer സ്റ്റാറ്റസ് കോഡിനെ കാണിക്കുന്നു
ഇത് നമക്ക് സ്ട്രീമിംഗ് ഉദാഹരണങ്ങൾ ഉണ്ടാക്കാം
```ruby
class Stream
def each
100.times { |i| yield "#{i}\n" }
end
end
get('/') { Stream.new }
```
`stream` helper' ഉപയോഗിച്ച([described below](#streaming-responses))റൂട്ട് ഇലെ ബോയ്ലർ പ്ലേറ്റ്സ് ഇനി കുറക്കാം
## Custom Route Matchers
മുകളിൽ കാണിച്ചിരിക്കുന്ന പോലെ , സിനാട്ര ഉപയോഗിച്ച String patterns, regular expressions കൈകാര്യം ചെയ്യാം മാത്രമല്ല നിങ്ങളുടെ സ്വന്തം 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
```
Or, using negative look ahead:
```ruby
get %r{(?!/index)} do
# ...
end
```
## Static Files
സ്റ്റാറ്റിക് ഫിലെസ് `./public` എന്ന ഡയറക്ടറി ഇത് ആണ് ഉണ്ടാകുക
നിങ്ങൾക്ക് `:public_folder` വഴി വേറെ പാത ഉണ്ടാക്കാം
```ruby
set :public_folder, File.dirname(__FILE__) + '/static'
```
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](#cache-control)) to add
`Cache-Control` header info.
## Views / Templates
എല്ലാ ടെമ്പ്ലേറ്റ് ഭാഷയും അതിന്റെ സ്വതം റെൻഡറിങ് മെതോഡിൽ ആണ് പുറത്തു കാണപ്പെടുക. ഇത് ഒരു സ്ട്രിംഗ് ഇനി റിട്ടേൺ ചെയ്യും
```ruby
get '/' do
erb :index
end
```
This renders `views/index.erb`.
ടെമ്പ്ലേറ്റ് ഇന്റെ പേരിനു പകരം നിങ്ങൾക്ക് ടെപ്ലേറ്റ് ഇന്റെ കോൺടെന്റ് കടത്തി വിടാം
```ruby
get '/' do
code = "<%= Time.now %>"
erb code
end
```
ടെമ്പ്ലേറ്റ് മറ്റൊരു അർജുമെന്റിനെ കടത്തി വിടുന്നു
```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).
സിനാട്ര ക്ക് മനസ്സിലാകാത്ത ടെമ്പ്ലേറ്റ് ഇനി ടെമ്പ്ലേറ്റ് എന്ജിനിലേക്ക് കടത്തി വിടും :
```ruby
get '/' do
haml :index, :format => :html5
end
```
നിങ്ങൾക്ക് ഓപ്ഷണൽ ആയി ലാംഗ്വേജ് ജനറലിൽ സെറ്റ് ചെയ്യാൻ കഴിയും :
```ruby
set :haml, :format => :html5
get '/' do
haml :index
end
```
ഉപയോഗിച്ച റെൻഡർ മെതോഡിൽ ഒപ്റേൻസ് പാസ് ചെയ്യാൻ പാട്ടും `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:
```ruby
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. You can optionally specify `:path` and
`:line` for a clearer backtrace if there is a filesystem path or line
associated with that string:
```ruby
get '/' do
haml '%div.title Hello World', :path => 'examples/file.haml', :line => 3
end
```
### 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](#inline-templates)).
#### Nokogiri Templates
Dependency |
nokogiri |
File Extension |
.nokogiri |
Example |
nokogiri { |xml| xml.em "hi" } |
It also takes a block for inline templates (see [example](#inline-templates)).
#### 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
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.
#### AsciiDoc Templates
Dependency |
Asciidoctor |
File Extension |
.asciidoc, .adoc and .ad |
Example |
asciidoc :README, :layout_engine => :erb |
Since you cannot call Ruby methods directly from an AsciiDoc template, you
almost always want to pass locals to it.
#### 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](#inline-templates)).
#### 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.
#### MediaWiki Templates
Dependency |
WikiCloth |
File Extension |
.mediawiki and .mw |
Example |
mediawiki :wiki, :layout_engine => :erb |
It is not possible to call methods from MediaWiki markup, nor to pass
locals to it. You therefore will usually use it in combination with
another rendering engine:
```ruby
erb :overview, :locals => { :text => mediawiki(:introduction) }
```
Note that you may also call the `mediawiki` method from within other
templates:
```ruby
%h1 Hello From Haml!
%p= mediawiki(:greetings)
```
Since you cannot call Ruby from MediaWiki, you cannot use layouts written in
MediaWiki. However, it is possible to use another rendering engine for the
template than for the layout by passing the `:layout_engine` option.
#### CoffeeScript Templates
#### Stylus Templates
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:
```javascript
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 methods 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`. Learn more about
[Tilt](https://github.com/rtomayko/tilt#readme).
### Using Custom Logic for Template Lookup
To implement your own template lookup mechanism you can write your
own `#find_template` method:
```ruby
configure do
set :views [ './views/a', './views/b' ]
end
def find_template(views, name, engine, &block)
Array(views).each do |v|
super(v, name, engine, &block)
end
end
```
## 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
as the routes will be 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
```
#### Session Secret Security
To improve security, the session data in the cookie is signed with a session
secret using `HMAC-SHA1`. This session secret should optimally be a
cryptographically secure random value of an appropriate length which for
`HMAC-SHA1` is greater than or equal to 64 bytes (512 bits, 128 hex
characters). You would be advised not to use a secret that is less than 32
bytes of randomness (256 bits, 64 hex characters). It is therefore **very
important** that you don't just make the secret up, but instead use a secure
random number generator to create it. Humans are extremely bad at generating
random values.
By default, a 32 byte secure random session secret is generated for you by
Sinatra, but it will change with every restart of your application. If you
have multiple instances of your application, and you let Sinatra generate the
key, each instance would then have a different session key which is probably
not what you want.
For better security and usability it's
[recommended](https://12factor.net/config) that you generate a secure random
secret and store it in an environment variable on each host running your
application so that all of your application instances will share the same
secret. You should periodically rotate this session secret to a new value.
Here are some examples of how you might create a 64 byte secret and set it:
**Session Secret Generation**
```text
$ ruby -e "require 'securerandom'; puts SecureRandom.hex(64)"
99ae8af...snip...ec0f262ac
```
**Session Secret Generation (Bonus Points)**
Use the [sysrandom gem](https://github.com/cryptosphere/sysrandom#readme) to
prefer use of system RNG facilities to generate random values instead of
userspace `OpenSSL` which MRI Ruby currently defaults to:
```text
$ gem install sysrandom
Building native extensions. This could take a while...
Successfully installed sysrandom-1.x
1 gem installed
$ ruby -e "require 'sysrandom/securerandom'; puts SecureRandom.hex(64)"
99ae8af...snip...ec0f262ac
```
**Session Secret Environment Variable**
Set a `SESSION_SECRET` environment variable for Sinatra to the value you
generated. Make this value persistent across reboots of your host. Since the
method for doing this will vary across systems this is for illustrative
purposes only:
```bash
# echo "export SESSION_SECRET=99ae8af...snip...ec0f262ac" >> ~/.bashrc
```
**Session Secret App Config**
Setup your app config to fail-safe to a secure random secret
if the `SESSION_SECRET` environment variable is not available.
For bonus points use the [sysrandom
gem](https://github.com/cryptosphere/sysrandom#readme) here as well:
```ruby
require 'securerandom'
# -or- require 'sysrandom/securerandom'
set :session_secret, ENV.fetch('SESSION_SECRET') { SecureRandom.hex(64) }
```
#### Session Config
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'
```
To share your session across other apps on subdomains of foo.com, prefix the
domain with a *.* like this instead:
```ruby
set :sessions, :domain => '.foo.com'
```
#### Choosing Your Own Session Middleware
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, one of the following methods can be used:
```ruby
enable :sessions
set :session_store, Rack::Session::Pool
```
Or to set up sessions with a hash of options:
```ruby
set :sessions, :expire_after => 2592000
set :session_store, Rack::Session::Pool
```
Another option is to **not** call `enable :sessions`, but instead pull in
your middleware of choice as you would any other middleware.
It is important to note that when using this method, session based
protection **will not be enabled by default**.
The Rack middleware to do that will also need to be added:
```ruby
use Rack::Session::Pool, :expire_after => 2592000
use Rack::Protection::RemoteToken
use Rack::Protection::SessionHijacking
```
See '[Configuring attack protection](#configuring-attack-protection)' for more information.
### 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"](#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; https://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](https://w3c.github.io/eventsource/), and can be used as
the basis for [WebSockets](https://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 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) do |out|
connections << out
# purge dead connections
connections.reject!(&:closed?)
end
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
```
It's also possible for the client to close the connection when trying to
write to the socket. Because of this, it's recommended to check
`out.closed?` before trying to write.
### 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 about it in your routes and filters.
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:
```ruby
%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](#browser-redirect) 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://www.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](https://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#readme):
```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](#cache-control)) 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 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
To return the contents of a file as the response, 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 to be used in the response,
defaults to the real file name.
- last_modified
- Value for Last-Modified header, defaults to the file's mtime.
- type
- Value for Content-Type header, guessed from the file extension if
missing.
- disposition
-
Value for Content-Disposition header, possible values: nil
(default), :attachment and :inline
- length
- Value for Content-Length header, defaults to file size.
- status
-
Status code to be sent. 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, 2016')
"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 (`APP_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/sinatra/sinatra/tree/master/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`
have been enabled. See '[Using Sessions](#using-sessions)'. Sometimes you may want to set up
sessions "outside" of the Sinatra app, such as in the config.ru or with a
separate `Rack::Builder` instance. In that case you can still set up session
based protection by passing the `:session` option:
```ruby
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_charset
-
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_charset << "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['APP_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 by default.
- method_override
-
Use _method magic to allow put/delete forms in browsers that
don't support it.
- mustermann_opts
-
A default hash of options to pass to Mustermann.new when compiling routing
paths.
- 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 by 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.
- quiet
-
Disables logs generated by Sinatra's start and stop commands.
false by default.
- 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.
- server_settings
-
If you are using a WEBrick web server, presumably for your development
environment, you can pass a hash of options to server_settings,
such as SSLEnable or SSLVerifyClient. However, web
servers such as Puma and Thin do not support this, so you can set
server_settings by defining it as a method when you call
configure.
- sessions
-
Enable cookie-based sessions support using
Rack::Session::Cookie. See 'Using Sessions' section for more
information.
- session_store
-
The Rack session middleware used. Defaults to
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 by 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.
- traps
- Whether Sinatra should handle system signals.
- 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
`APP_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 `APP_ENV` environment variable:
```shell
APP_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. But note in development it will only run if you set the
show exceptions option to `:after_handler`:
```ruby
set :show_exceptions, :after_handler
```
The exception object can be obtained from the `sinatra.error` Rack variable:
```ruby
error do
'Sorry there was a nasty error - ' + env['sinatra.error'].message
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](https://rack.github.io/), 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://www.rubydoc.info/github/rack/rack/master/Rack/Builder) 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#readme),
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://www.rubydoc.info/github/brynary/rack-test/master/frames)
is recommended:
```ruby
require 'my_sinatra_app'
require 'minitest/autorun'
require 'rack/test'
class MyAppTest < Minitest::Test
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_user_agent
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 [Configuring
Settings](http://www.sinatrarb.com/configuration.html) for details on
available options and their behavior. If you want behavior more similar
to when you define your app at the top level (also known as Classic
style), you can subclass `Sinatra::Application`:
```ruby
require 'sinatra/base'
class MyApp < Sinatra::Application
get '/' do
'Hello world!'
end
end
```
### 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 |
Modular |
app_file |
file loading sinatra |
file subclassing Sinatra::Base |
file subclassing Sinatra::Application |
run |
$0 == app_file |
false |
false |
logging |
true |
false |
true |
method_override |
true |
false |
true |
inline_templates |
true |
false |
true |
static |
true |
File.exist?(public_folder) |
true |
### 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:
```shell
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:
```shell
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/Hanami/Roda/...):
```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:
```shell
ruby myapp.rb [-h] [-x] [-q] [-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)
-q # turn on quiet mode for server (default is off)
-x # turn on the mutex lock (default is off)
```
### Multi-threading
_Paraphrasing from
[this StackOverflow answer](https://stackoverflow.com/a/6282999/5245129)
by Konstantin_
Sinatra doesn't impose any concurrency model, but leaves that to the
underlying Rack handler (server) like Thin, Puma or WEBrick. Sinatra
itself is thread-safe, so there won't be any problem if the Rack handler
uses a threaded model of concurrency. This would mean that when starting
the server, you'd have to specify the correct invocation method for the
specific Rack handler. The following example is a demonstration of how
to start a multi-threaded Thin server:
```ruby
# app.rb
require 'sinatra/base'
class App < Sinatra::Base
get '/' do
"Hello, World"
end
end
App.run!
```
To start the server, the command would be:
```shell
thin --threaded start
```
## Requirement
The following Ruby versions are officially supported:
- Ruby 2.2
-
2.2 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 recommended 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.
Versions of Ruby prior to 2.2.2 are no longer supported as of Sinatra 2.0.
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 (future releases of MRI), but we
can't guarantee anything, since it is constantly moving. Expect upcoming
2.x releases 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 2.2.
## 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](https://bundler.io) 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
```
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
```
## Versioning
Sinatra follows [Semantic Versioning](https://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](https://github.com/sinatra/sinatra/issues)
* [Twitter](https://twitter.com/sinatra)
* [Mailing List](https://groups.google.com/forum/#!forum/sinatrarb)
* IRC: [#sinatra](irc://chat.freenode.net/#sinatra) on [Freenode](https://freenode.net)
* [Sinatra & Friends](https://sinatrarb.slack.com) on Slack
([get an invite](https://sinatra-slack.herokuapp.com/))
* [Sinatra Book](https://github.com/sinatra/sinatra-book) - Cookbook Tutorial
* [Sinatra Recipes](http://recipes.sinatrarb.com/) - Community contributed
recipes
* API documentation for the [latest release](http://www.rubydoc.info/gems/sinatra)
or the [current HEAD](http://www.rubydoc.info/github/sinatra/sinatra) on
[RubyDoc](http://www.rubydoc.info/)
* [CI server](https://travis-ci.org/sinatra/sinatra)
sinatra-2.0.8.1/README.md 0000664 0000000 0000000 00000232335 13603175240 0014611 0 ustar 00root root 0000000 0000000 # Sinatra
[](http://badge.fury.io/rb/sinatra)
[](https://travis-ci.org/sinatra/sinatra)
[](https://dependabot.com/compatibility-score.html?dependency-name=sinatra&package-manager=bundler&version-scheme=semver)
Sinatra is a [DSL](https://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](http://localhost:4567)
The code you changed will not take effect until you restart the server.
Please restart the server every time you change or use
[sinatra/reloader](http://www.sinatrarb.com/contrib/reloader).
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)
* [AsciiDoc Templates](#asciidoc-templates)
* [Radius Templates](#radius-templates)
* [Markaby Templates](#markaby-templates)
* [RABL Templates](#rabl-templates)
* [Slim Templates](#slim-templates)
* [Creole Templates](#creole-templates)
* [MediaWiki Templates](#mediawiki-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)
* [Using Custom Logic for Template Lookup](#using-custom-logic-for-template-lookup)
* [Filters](#filters)
* [Helpers](#helpers)
* [Using Sessions](#using-sessions)
* [Session Secret Security](#session-secret-security)
* [Session Config](#session-config)
* [Choosing Your Own Session Middleware](#choosing-your-own-session-middleware)
* [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)
* [Multi-threading](#multi-threading)
* [Requirement](#requirement)
* [The Bleeding Edge](#the-bleeding-edge)
* [With Bundler](#with-bundler)
* [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.
Routes with trailing slashes are different from the ones without:
```ruby
get '/foo' do
# Does not match "GET /foo/"
end
```
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 /\/hello\/([\w]+)/ do
"Hello, #{params['captures'].first}!"
end
```
Or with a block parameter:
```ruby
get %r{/hello/([\w]+)} do |c|
# Matches "GET /meta/hello/world", "GET /hello/world/1234" etc.
"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
```
Routes may also utilize query parameters:
```ruby
get '/posts' do
# matches "GET /posts?title=foo&author=bar"
title = params['title']
author = params['author']
# uses title and author variables; query is optional to the /posts route
end
```
By the way, unless you disable the path traversal attack protection (see
[below](#configuring-attack-protection)), the request path might be modified before
matching against your routes.
You may customize the [Mustermann](https://github.com/sinatra/mustermann#readme)
options used for a given route by passing in a `:mustermann_opts` hash:
```ruby
get '\A/posts\z', :mustermann_opts => { :type => :regexp, :check_anchors => false } do
# matches /posts exactly, with explicit anchoring
"If you match an anchored pattern clap your hands!"
end
```
It looks like a [condition](#conditions), but it isn't one! These options will
be merged into the global `:mustermann_opts` hash described
[below](#available-settings).
## 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
```
`provides` searches the request's Accept header.
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 (Integer), headers (Hash), response
body (responds to #each)]`
* An Array with two elements: `[status (Integer), response body (responds to
#each)]`
* An object that responds to `#each` and passes nothing but strings to
the given block
* A Integer 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](#streaming-responses)) 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](#cache-control)) 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:
```ruby
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. You can optionally specify `:path` and
`:line` for a clearer backtrace if there is a filesystem path or line
associated with that string:
```ruby
get '/' do
haml '%div.title Hello World', :path => 'examples/file.haml', :line => 3
end
```
### 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 |
erubi
or erubis
or erb (included in Ruby)
|
File Extensions |
.erb, .rhtml or .erubi (Erubi only)
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](#inline-templates)).
#### Nokogiri Templates
Dependency |
nokogiri |
File Extension |
.nokogiri |
Example |
nokogiri { |xml| xml.em "hi" } |
It also takes a block for inline templates (see [example](#inline-templates)).
#### 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
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.
#### AsciiDoc Templates
Dependency |
Asciidoctor |
File Extension |
.asciidoc, .adoc and .ad |
Example |
asciidoc :README, :layout_engine => :erb |
Since you cannot call Ruby methods directly from an AsciiDoc template, you
almost always want to pass locals to it.
#### 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](#inline-templates)).
#### 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.
#### MediaWiki Templates
Dependency |
WikiCloth |
File Extension |
.mediawiki and .mw |
Example |
mediawiki :wiki, :layout_engine => :erb |
It is not possible to call methods from MediaWiki markup, nor to pass
locals to it. You therefore will usually use it in combination with
another rendering engine:
```ruby
erb :overview, :locals => { :text => mediawiki(:introduction) }
```
Note that you may also call the `mediawiki` method from within other
templates:
```ruby
%h1 Hello From Haml!
%p= mediawiki(:greetings)
```
Since you cannot call Ruby from MediaWiki, you cannot use layouts written in
MediaWiki. However, it is possible to use another rendering engine for the
template than for the layout by passing the `:layout_engine` option.
#### CoffeeScript Templates
#### Stylus Templates
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:
```javascript
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 methods 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`. Learn more about
[Tilt](https://github.com/rtomayko/tilt#readme).
### Using Custom Logic for Template Lookup
To implement your own template lookup mechanism you can write your
own `#find_template` method:
```ruby
configure do
set :views [ './views/a', './views/b' ]
end
def find_template(views, name, engine, &block)
Array(views).each do |v|
super(v, name, engine, &block)
end
end
```
## 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
as the routes will be 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
```
#### Session Secret Security
To improve security, the session data in the cookie is signed with a session
secret using `HMAC-SHA1`. This session secret should optimally be a
cryptographically secure random value of an appropriate length which for
`HMAC-SHA1` is greater than or equal to 64 bytes (512 bits, 128 hex
characters). You would be advised not to use a secret that is less than 32
bytes of randomness (256 bits, 64 hex characters). It is therefore **very
important** that you don't just make the secret up, but instead use a secure
random number generator to create it. Humans are extremely bad at generating
random values.
By default, a 32 byte secure random session secret is generated for you by
Sinatra, but it will change with every restart of your application. If you
have multiple instances of your application, and you let Sinatra generate the
key, each instance would then have a different session key which is probably
not what you want.
For better security and usability it's
[recommended](https://12factor.net/config) that you generate a secure random
secret and store it in an environment variable on each host running your
application so that all of your application instances will share the same
secret. You should periodically rotate this session secret to a new value.
Here are some examples of how you might create a 64 byte secret and set it:
**Session Secret Generation**
```text
$ ruby -e "require 'securerandom'; puts SecureRandom.hex(64)"
99ae8af...snip...ec0f262ac
```
**Session Secret Generation (Bonus Points)**
Use the [sysrandom gem](https://github.com/cryptosphere/sysrandom#readme) to
prefer use of system RNG facilities to generate random values instead of
userspace `OpenSSL` which MRI Ruby currently defaults to:
```text
$ gem install sysrandom
Building native extensions. This could take a while...
Successfully installed sysrandom-1.x
1 gem installed
$ ruby -e "require 'sysrandom/securerandom'; puts SecureRandom.hex(64)"
99ae8af...snip...ec0f262ac
```
**Session Secret Environment Variable**
Set a `SESSION_SECRET` environment variable for Sinatra to the value you
generated. Make this value persistent across reboots of your host. Since the
method for doing this will vary across systems this is for illustrative
purposes only:
```bash
# echo "export SESSION_SECRET=99ae8af...snip...ec0f262ac" >> ~/.bashrc
```
**Session Secret App Config**
Setup your app config to fail-safe to a secure random secret
if the `SESSION_SECRET` environment variable is not available.
For bonus points use the [sysrandom
gem](https://github.com/cryptosphere/sysrandom#readme) here as well:
```ruby
require 'securerandom'
# -or- require 'sysrandom/securerandom'
set :session_secret, ENV.fetch('SESSION_SECRET') { SecureRandom.hex(64) }
```
#### Session Config
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'
```
To share your session across other apps on subdomains of foo.com, prefix the
domain with a *.* like this instead:
```ruby
set :sessions, :domain => '.foo.com'
```
#### Choosing Your Own Session Middleware
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, one of the following methods can be used:
```ruby
enable :sessions
set :session_store, Rack::Session::Pool
```
Or to set up sessions with a hash of options:
```ruby
set :sessions, :expire_after => 2592000
set :session_store, Rack::Session::Pool
```
Another option is to **not** call `enable :sessions`, but instead pull in
your middleware of choice as you would any other middleware.
It is important to note that when using this method, session based
protection **will not be enabled by default**.
The Rack middleware to do that will also need to be added:
```ruby
use Rack::Session::Pool, :expire_after => 2592000
use Rack::Protection::RemoteToken
use Rack::Protection::SessionHijacking
```
See '[Configuring attack protection](#configuring-attack-protection)' for more information.
### 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"](#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; https://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](https://w3c.github.io/eventsource/), and can be used as
the basis for [WebSockets](https://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 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) do |out|
connections << out
# purge dead connections
connections.reject!(&:closed?)
end
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
```
It's also possible for the client to close the connection when trying to
write to the socket. Because of this, it's recommended to check
`out.closed?` before trying to write.
### 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 about it in your routes and filters.
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:
```ruby
%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](#browser-redirect) 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://www.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](https://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#readme):
```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](#cache-control)) 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 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
To return the contents of a file as the response, 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 to be used in the response,
defaults to the real file name.
- last_modified
- Value for Last-Modified header, defaults to the file's mtime.
- type
- Value for Content-Type header, guessed from the file extension if
missing.
- disposition
-
Value for Content-Disposition header, possible values: nil
(default), :attachment and :inline
- length
- Value for Content-Length header, defaults to file size.
- status
-
Status code to be sent. 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, 2016')
"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 (`APP_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/sinatra/sinatra/tree/master/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`
have been enabled. See '[Using Sessions](#using-sessions)'. Sometimes you may want to set up
sessions "outside" of the Sinatra app, such as in the config.ru or with a
separate `Rack::Builder` instance. In that case you can still set up session
based protection by passing the `:session` option:
```ruby
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_charset
-
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_charset << "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['APP_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 by default.
- method_override
-
Use _method magic to allow put/delete forms in browsers that
don't support it.
- mustermann_opts
-
A default hash of options to pass to Mustermann.new when compiling routing
paths.
- 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 by 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.
- quiet
-
Disables logs generated by Sinatra's start and stop commands.
false by default.
- 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.
- server_settings
-
If you are using a WEBrick web server, presumably for your development
environment, you can pass a hash of options to server_settings,
such as SSLEnable or SSLVerifyClient. However, web
servers such as Puma and Thin do not support this, so you can set
server_settings by defining it as a method when you call
configure.
- sessions
-
Enable cookie-based sessions support using
Rack::Session::Cookie. See 'Using Sessions' section for more
information.
- session_store
-
The Rack session middleware used. Defaults to
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 by 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.
- traps
- Whether Sinatra should handle system signals.
- 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
`APP_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 `APP_ENV` environment variable:
```shell
APP_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. But note in development it will only run if you set the
show exceptions option to `:after_handler`:
```ruby
set :show_exceptions, :after_handler
```
The exception object can be obtained from the `sinatra.error` Rack variable:
```ruby
error do
'Sorry there was a nasty error - ' + env['sinatra.error'].message
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](https://rack.github.io/), 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://www.rubydoc.info/github/rack/rack/master/Rack/Builder) 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#readme),
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://www.rubydoc.info/github/brynary/rack-test/master/frames)
is recommended:
```ruby
require 'my_sinatra_app'
require 'minitest/autorun'
require 'rack/test'
class MyAppTest < Minitest::Test
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_user_agent
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 [Configuring
Settings](http://www.sinatrarb.com/configuration.html) for details on
available options and their behavior. If you want behavior more similar
to when you define your app at the top level (also known as Classic
style), you can subclass `Sinatra::Application`:
```ruby
require 'sinatra/base'
class MyApp < Sinatra::Application
get '/' do
'Hello world!'
end
end
```
### 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 |
Modular |
app_file |
file loading sinatra |
file subclassing Sinatra::Base |
file subclassing Sinatra::Application |
run |
$0 == app_file |
false |
false |
logging |
true |
false |
true |
method_override |
true |
false |
true |
inline_templates |
true |
false |
true |
static |
true |
File.exist?(public_folder) |
true |
### 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:
```shell
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:
```shell
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/Hanami/Roda/...):
```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:
```shell
ruby myapp.rb [-h] [-x] [-q] [-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)
-q # turn on quiet mode for server (default is off)
-x # turn on the mutex lock (default is off)
```
### Multi-threading
_Paraphrasing from
[this StackOverflow answer](https://stackoverflow.com/a/6282999/5245129)
by Konstantin_
Sinatra doesn't impose any concurrency model, but leaves that to the
underlying Rack handler (server) like Thin, Puma or WEBrick. Sinatra
itself is thread-safe, so there won't be any problem if the Rack handler
uses a threaded model of concurrency. This would mean that when starting
the server, you'd have to specify the correct invocation method for the
specific Rack handler. The following example is a demonstration of how
to start a multi-threaded Thin server:
```ruby
# app.rb
require 'sinatra/base'
class App < Sinatra::Base
get '/' do
"Hello, World"
end
end
App.run!
```
To start the server, the command would be:
```shell
thin --threaded start
```
## Requirement
The following Ruby versions are officially supported:
- Ruby 2.2
-
2.2 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 recommended 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.
Versions of Ruby prior to 2.2.2 are no longer supported as of Sinatra 2.0.
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 (future releases of MRI), but we
can't guarantee anything, since it is constantly moving. Expect upcoming
2.x releases 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 2.2.
## 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](https://bundler.io) 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
```
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
```
## Versioning
Sinatra follows [Semantic Versioning](https://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](https://github.com/sinatra/sinatra/issues)
* [Twitter](https://twitter.com/sinatra)
* [Mailing List](https://groups.google.com/forum/#!forum/sinatrarb)
* IRC: [#sinatra](irc://chat.freenode.net/#sinatra) on [Freenode](https://freenode.net)
* [Sinatra & Friends](https://sinatrarb.slack.com) on Slack
([get an invite](https://sinatra-slack.herokuapp.com/))
* [Sinatra Book](https://github.com/sinatra/sinatra-book) - Cookbook Tutorial
* [Sinatra Recipes](http://recipes.sinatrarb.com/) - Community contributed
recipes
* API documentation for the [latest release](http://www.rubydoc.info/gems/sinatra)
or the [current HEAD](http://www.rubydoc.info/github/sinatra/sinatra) on
[RubyDoc](http://www.rubydoc.info/)
* [CI server](https://travis-ci.org/sinatra/sinatra)
sinatra-2.0.8.1/README.pt-br.md 0000664 0000000 0000000 00000304000 13603175240 0015621 0 ustar 00root root 0000000 0000000 # Sinatra
*Atenção: Este documento é apenas uma tradução da versão em inglês e
pode estar desatualizado.*
Alguns dos trechos de código a seguir utilizam caracteres UTF-8. Então, caso
esteja utilizando uma versão de ruby inferior à `2.0.0`, adicione o encoding
no início de seus arquivos:
```ruby
# encoding: utf-8
```
Sinatra é uma
[DSL](https://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
# minha_app.rb
require 'sinatra'
get '/' do
'Olá Mundo!'
end
```
Instale a gem:
```shell
gem install sinatra
```
Em seguida execute:
```shell
ruby minha_app.rb
```
Acesse em: [http://localhost:4567](http://localhost:4567)
Códigos alterados só terão efeito após você reiniciar o servidor.
Por favor, reinicie o servidor após qualquer mudança ou use
[sinatra/reloader](http://www.sinatrarb.com/contrib/reloader).
É recomendado também executar `gem install thin`. Caso esta gem esteja
disponível, o Sinatra irá utilizá-la.
## Conteúdo
* [Sinatra](#sinatra)
* [Conteúdo](#conteúdo)
* [Rotas](#rotas)
* [Condições](#condições)
* [Valores Retornados](#valores-retornados)
* [Validadores de rota personalizados](#validadores-de-rota-personalizados)
* [Arquivos estáticos](#arquivos-estáticos)
* [Views / Templates](#views--templates)
* [Literal Templates](#literal-templates)
* [Linguagens de template disponíveis](#linguagens-de-template-disponíveis)
* [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)
* [AsciiDoc Templates](#asciidoc-templates)
* [Radius Templates](#radius-templates)
* [Markaby Templates](#markaby-templates)
* [RABL Templates](#rabl-templates)
* [Slim Templates](#slim-templates)
* [Creole Templates](#creole-templates)
* [MediaWiki Templates](#mediawiki-templates)
* [CoffeeScript Templates](#coffeescript-templates)
* [Stylus Templates](#stylus-templates)
* [Yajl Templates](#yajl-templates)
* [WLang Templates](#wlang-templates)
* [Acessando Variáveis nos Templates](#acessando-variáveis-nos-templates)
* [Templates com `yield` e layouts aninhados](#templates-com-yield-e-layouts-aninhados)
* [Templates Inline](#templates-inline)
* [Templates Nomeados](#templates-nomeados)
* [Associando extensões de arquivos](#associando-extensões-de-arquivos)
* [Adicionando seu Próprio Engine de Template](#adicionando-seu-próprio-engine-de-template)
* [Customizando lógica para encontrar templates](#customizando-lógica-para-encontrar-templates)
* [Filtros](#filtros)
* [Helpers](#helpers)
* [Utilizando Sessões](#utilizando-sessões)
* [Segurança Secreta da Sessão](#segurança-secreta-da-sessão)
* [Configuração da Sessão](#configuração-da-sessão)
* [Escolhande Seu Próprio Middleware de Sessão](#escolhendo-middleware-de-sessão)
* [Halting](#halting)
* [Passing](#passing)
* [Desencadeando Outra Rota](#desencadeando-outra-rota)
* [Definindo Corpo, Código de Status e Cabeçalhos](#definindo-corpo-codigo-de-status-cabeçalhos)
* [Transmitindo Respostas](#transmitindo-respostas)
* [Usando Logs](#usando-logs)
* [Tipos Mime](#tipos-mime)
* [Gerando URLS](#gerando-urls)
* [Redirecionamento do Navegador](#redirecionamento-do-navagador)
* [Controle de Cache](#controle-de-cache)
* [Enviando Arquivos](#enviando-arquivos)
* [Acessando o Objeto de Requisição](#acessando-o-objeto-de-requisição)
* [Anexos](#anexos)
* [Trabalhando com Data e Hora](#trabalhando-com-data-e-hora)
* [Procurando Arquivos de Modelo](#procurando-arquivos-de-modelo)
* [Configuração](#configuração)
* [Configurando proteção a ataques](#configurando-proteção-a-ataques)
* [Configurações Disponíveis](#configurações-disponíveis)
* [Ambientes](#ambientes)
* [Tratamento de Erros](#tratamento-de-erros)
* [Não Encontrado](#não-encontrado)
* [Erro](#erro)
* [Rack Middleware](#rack-middleware)
* [Testando](#testando)
* [Sinatra::Base - Middleware, Bibliotecas e Aplicações Modulares](#sinatrabase-middleware-bibliotecas-e-aplicações-modulares)
* [Modular vs. Estilo Clássico](#modular-vs-estilo-clássico)
* [Servindo uma Aplicação Modular](#servindo-uma-aplicação-modular)
* [Usando uma Aplicação de Estilo Clássico com um config.ru](#usando-uma-aplicação-de-estilo-clássico-com-um-configru)
* [Quando usar um config.ru?](#quando-usar-um-configru)
* [Usando Sinatra como Middleware](#usando-sinatra-como-middleware)
* [Criação de Aplicações Dinâmicas](#criação-de-aplicações-dinamicas)
* [Escopos e Ligação](#escopos-e-ligação)
* [Escopo de Aplicação/Classe](#escopo-de-aplicação-classe)
* [Escopo de Instância/Requisição](#escopo-de-instância-requisição)
* [Escopo de Delegação](#escopo-de-delegação)
* [Linha de comando](#linha-de-comando)
* [Multi-threading](#multi-threading)
* [Requerimentos](#requerimentos)
* [A última versão](#a-última-versão)
* [Com Bundler](#com-bundler)
* [Versionando](#versionando)
* [Mais](#mais)
## 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
link '/' do
.. associando alguma coisa ..
end
unlink '/' do
.. separando alguma coisa ..
end
```
As rotas são interpretadas na ordem em que são definidas. A primeira
rota encontrada responde a requisição.
Rotas com barras à direita são diferentes das que não contém as barras:
```ruby
get '/foo' do
# Não é o mesmo que "GET /foo/"
end
```
Padrões de rota podem conter parâmetros nomeados, acessíveis por meio 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 por meio dos parâmetros de
um bloco:
```ruby
get '/ola/:nome' do |n|
# corresponde a "GET /ola/foo" e "GET /ola/bar"
# params['nome'] é 'foo' ou 'bar'
# n guarda o valor de params['nome']
"Olá #{n}!"
end
```
Padrões de rota também podem conter parâmetros splat (curinga),
acessível por meio 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/caminho/do/arquivo.xml
params['splat'] # => ["caminho/do/arquivo", "xml"]
end
```
Ou com parâmetros de um bloco:
```ruby
get '/download/*.*' do |caminho, ext|
[caminho, ext] # => ["caminho/do/arquivo", "xml"]
end
```
Rotas podem casar com expressões regulares:
```ruby
get /\/ola\/([\w]+)/ do
"Olá, #{params['captures'].first}!"
end
```
Ou com parâmetros de um bloco:
```ruby
get %r{/ola/([\w]+)} do |c|
# corresponde a "GET /meta/ola/mundo", "GET /ola/mundo/1234" etc.
"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
```
Rotas também podem utilizar query strings:
```ruby
get '/posts' do
# corresponde a "GET /posts?titulo=foo&autor=bar"
titulo = params['titulo']
autor = params['autor']
# utiliza as variaveis titulo e autor; a query é opicional para a rota /posts
end
```
A propósito, a menos que você desative a proteção contra ataques (veja
[abaixo](#configurando-proteção-a-ataques)), o caminho solicitado pode ser
alterado antes de concluir a comparação com as suas rotas.
Você pode customizar as opções usadas do
[Mustermann](https://github.com/sinatra/mustermann#readme) para uma
rota passando `:mustermann_opts` num hash:
```ruby
get '\A/posts\z', :musterman_opts => { :type => regexp, :check_anchors => false } do
# corresponde a /posts exatamente, com ancoragem explícita
"Se você combinar um padrão ancorado bata palmas!"
end
```
Parece com uma [condição](#condições) mas não é! Essas opções serão
misturadas no hash global `:mustermann_opts` descrito
[abaixo](#configurações-disponíveis)
## 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
```
`provides` procura o cabeçalho Accept das requisições
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 leva 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. Entretanto, 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 disso, é possível
retornar um código de status HTTP.
* Um array com três elementros: `[status (Integer), cabecalho (Hash),
corpo da resposta (responde à #each)]`
* Um array com dois elementros: `[status (Integer), corpo da resposta
(responde à #each)]`
* Um objeto que responda à `#each` sem passar nada, mas, sim, `strings`
para um dado bloco
* Um objeto `Integer` 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](#respostas-de-streaming)) para reduzir códigos
boilerplate e para incorporar a lógica de streaming (transmissão) na rota.
## Validadores de Rota Personalizados
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://exemplo.com/css/style.css`.
Use a configuração `:static_cache_control` (veja [abaixo](#controle-de-cache))
para adicionar a informação `Cache-Control` no cabeçalho.
## Views / Templates
Cada linguagem de template é exposta através de seu próprio método de
renderização. Estes métodos simplesmente retornam uma string:
```ruby
get '/' do
erb :index
end
```
Isto renderiza `views/index.rb`
Ao invés do nome do template, você também pode passar direto o conteúdo do
template:
```ruby
get '/' do
code = "<%= Time.now %>"
erb code
end
```
Templates também aceitam como um segundo argumento, um hash de opções:
```ruby
get '/' do
erb :index, :layout => :post
end
```
Isto irá renderizar a `views/index.erb` inclusa dentro da `views/post.erb`
(o padrão é a `views/layout.erb`, se existir).
Qualquer opção não reconhecida pelo Sinatra será passada adiante para o engine
de template:
```ruby
get '/' do
haml :index, :format => :html5
end
```
Você também pode definir opções padrões para um tipo de template:
```ruby
set :haml, :format => :html5
get '/' do
haml :index
end
```
Opções passadas para o método de renderização sobrescreve as opções definitas
através do método `set`.
Opções disponíveis:
- locals
-
Lista de locais passado para o documento. Conveniente para *partials*
Exemplo: erb "<%= foo %>", :locals => {:foo => "bar"}
- default_encoding
-
String encoding para ser utilizada em caso de incerteza. o padrão é
settings.default_encoding.
- views
-
Diretório de onde os templates são carregados. O padrão é
settings.views.
- layout
-
Para definir quando utilizar ou não um
layout (true ou false). E se for um
Symbol, especifica qual template usar. Exemplo:
erb :index, :layout => !request.xhr?
- content_type
-
O *Content-Type* que o template produz. O padrão depente
da linguagem de template utilizada.
- scope
-
Escopo em que o template será renderizado. Padrão é a
instância da aplicação. Se você mudar isto as variáveis
de instância métodos auxiliares não serão
disponibilizados.
- layout_engine
-
A engine de template utilizada para renderizar seu layout.
Útil para linguagens que não suportam templates de outra
forma. O padrão é a engine do template utilizado. Exemplo:
set :rdoc, :layout_engine => :erb
- layout_options
-
Opções especiais utilizadas apenas para renderizar o
layout. Exemplo:
set :rdoc, :layout_options => { :views => 'views/layouts' }
É pressuposto que os templates estarão localizados diretamente sob o
diretório `./views`. Para usar um diretório diferente:
```ruby
set :views, settings.root + '/templates'
```
Uma coisa importante para se lembrar é que você sempre deve
referenciar os templates utilizando *symbols*, mesmo que
eles estejam em um subdiretório (neste caso use:
`:'subdir/template'` or `'subdir/template'.to_sym`). Você deve
utilizar um *symbol* porque senão o método de renderização irá
renderizar qualquer outra string que você passe diretamente
para ele
### Literal Templates
```ruby
get '/' do
haml '%div.title Olá Mundo'
end
```
Renderiza um template string. Você pode opcionalmente especificar `path`
e `:line` para um backtrace mais claro se existir um caminho do sistema de
arquivos ou linha associada com aquela string.
```ruby
get '/' do
haml '%div.title Olá Mundo', :path => 'exemplos/arquivo.haml', :line => 3
end
```
### Linguagens de template disponíveis
Algumas linguagens possuem multiplas implementações. Para especificar qual
implementação deverá ser utilizada (e para ser *thread-safe*), você deve
requere-la primeiro:
```ruby
require 'rdiscount' # ou require 'bluecloth'
get('/') { markdown :index }
```
#### Haml Templates
Dependência |
haml |
Extensão do Arquivo |
.haml |
Exemplo |
haml :index, :format => :html5 |
#### Erb Templates
Dependência |
erubis
or erb (included in Ruby)
|
Extensão dos Arquivos |
.erb, .rhtml or .erubis (Erubis only) |
Exemplo |
erb :index |
#### Builder Templates
Dependêcia |
builder
|
Extensão do Arquivo |
.builder |
Exemplo |
builder { |xml| xml.em "hi" } |
It also takes a block for inline templates (see exemplo).
#### Nokogiri Templates
Dependência |
nokogiri |
Extensão do Arquivo |
.nokogiri |
Exemplo |
nokogiri { |xml| xml.em "hi" } |
It also takes a block for inline templates (see exemplo).
#### Sass Templates
Dependência |
sass |
Extensão do Arquivo |
.sass |
Exemplo |
sass :stylesheet, :style => :expanded |
#### SCSS Templates
Dependência |
sass |
Extensão do Arquivo |
.scss |
Exemplo |
scss :stylesheet, :style => :expanded |
#### Less Templates
Dependência |
less |
Extensão do Arquivo |
.less |
Exemplo |
less :stylesheet |
#### Liquid Templates
Dependência |
liquid
|
Extensão do Arquivo |
.liquid |
Exemplo |
liquid :index, :locals => { :key => 'value' } |
Já que você não pode chamar o Ruby (exceto pelo método `yield`) pelo template
Liquid, você quase sempre precisará passar o `locals` para ele.
#### Markdown Templates
Não é possível chamar métodos por este template, nem passar *locals* para o
mesmo. Portanto normalmente é utilizado junto a outra engine de renderização:
```ruby
erb :overview, :locals => { :text => markdown(:introducao) }
```
Note que vcoê também pode chamar o método `markdown` dentro de outros templates:
```ruby
%h1 Olá do Haml!
%p= markdown(:saudacoes)
```
Já que você não pode chamar o Ruby pelo Markdown, você não
pode utilizar um layout escrito em Markdown. Contudo é
possível utilizar outra engine de renderização como template,
deve-se passar a `:layout_engine` como opção.
Dependência |
RedCloth |
Extensão do Arquivo |
.textile |
Exemplo |
textile :index, :layout_engine => :erb |
Não é possível chamar métodos por este template, nem passar *locals* para o
mesmo. Portanto normalmente é utilizado junto a outra engine de renderização:
```ruby
erb :overview, :locals => { :text => textile(:introducao) }
```
Note que vcoê também pode chamar o método `textile` dentro de outros templates:
```ruby
%h1 Olá do Haml!
%p= textile(:saudacoes)
```
Já que você não pode chamar o Ruby pelo Textile, você não
pode utilizar um layout escrito em Textile. Contudo é
possível utilizar outra engine de renderização como template,
deve-se passar a `:layout_engine` como opção.
#### RDoc Templates
Dependência |
RDoc |
Extensão do Arquivo |
.rdoc |
Exemplo |
rdoc :README, :layout_engine => :erb |
Não é possível chamar métodos por este template, nem passar *locals* para o
mesmo. Portanto normalmente é utilizado junto a outra engine de renderização:
```ruby
erb :overview, :locals => { :text => rdoc(:introducao) }
```
Note que vcoê também pode chamar o método `rdoc` dentro de outros templates:
```ruby
%h1 Olá do Haml!
%p= rdoc(:saudacoes)
```
Já que você não pode chamar o Ruby pelo RDoc, você não
pode utilizar um layout escrito em RDoc. Contudo é
possível utilizar outra engine de renderização como template,
deve-se passar a `:layout_engine` como opção.
#### AsciiDoc Templates
Dependência |
Asciidoctor
|
Extensão do Arquivo |
.asciidoc, .adoc and .ad |
Exemplo |
asciidoc :README, :layout_engine => :erb |
Já que você não pode chamar o Ruby pelo template AsciiDoc,
você quase sempre precisará passar o `locals` para ele.
#### Radius Templates
Dependência |
Radius |
Extensão do Arquivo |
.radius |
Exemplo |
radius :index, :locals => { :key => 'value' } |
Já que você não pode chamar o Ruby pelo template Radius,
você quase sempre precisará passar o `locals` para ele.
#### Markaby Templates
Dependência |
Markaby |
Extensão do Arquivo |
.mab |
Exemplo |
markaby { h1 "Welcome!" } |
Este também recebe um bloco para templates (veja o exemplo).
#### RABL Templates
Dependência |
Rabl |
Extensão do Arquivo |
.rabl |
Exemplo |
rabl :index |
#### Slim Templates
Dependência |
Slim Lang |
Extensão do Arquivo |
.slim |
Exemplo |
slim :index |
#### Creole Templates
Dependência |
Creole |
Extensão do Arquivo |
.creole |
Exemplo |
creole :wiki, :layout_engine => :erb |
Não é possível chamar métodos por este template, nem passar *locals* para o
mesmo. Portanto normalmente é utilizado junto a outra engine de renderização:
```ruby
erb :overview, :locals => { :text => creole(:introduction) }
```
Note que você também pode chamar o método `creole` dentro de outros templates:
```ruby
%h1 Olá do Haml!
%p= creole(:saudacoes)
```
Já que você não pode chamar o Ruby pelo Creole, você não
pode utilizar um layout escrito em Creole. Contudo é
possível utilizar outra engine de renderização como template,
deve-se passar a `:layout_engine` como opção.
#### MediaWiki Templates
Dependência |
WikiCloth
|
Extensão do Arquivo |
.mediawiki and .mw |
Exemplo |
mediawiki :wiki, :layout_engine => :erb |
It is not possible to call methods from MediaWiki markup, nor to pass locals to
it. You therefore will usually use it in combination with another rendering
engine:
```ruby
erb :overview, :locals => { :text => mediawiki(:introduction) }
```
Note that you may also call the `mediawiki` method from within other templates:
```ruby
%h1 Hello From Haml!
%p= mediawiki(:greetings)
```
Já que você não pode chamar o Ruby pelo MediaWiki, você não
pode utilizar um layout escrito em MediaWiki. Contudo é
possível utilizar outra engine de renderização como template,
deve-se passar a `:layout_engine` como opção.
#### CoffeeScript Templates
#### Stylus Templates
Antes que vcoê possa utilizar o template Stylus primeiro você deve carregar
`stylus` e `stylus/tilt`:
```ruby
require 'sinatra'
require 'stylus'
require 'stylus/tilt'
get '/' do
stylus :exemplo
end
```
#### Yajl Templates
Dependência |
yajl-ruby
|
Extensão do Arquivo |
.yajl |
Exemplo |
yajl :index,
:locals => { :key => 'qux' },
:callback => 'present',
:variable => 'resource'
|
O código-fonte do template é executado como uma string Ruby e a variável
resultante em json é convertida utilizando `#to_json`:
```ruby
json = { :foo => 'bar' }
json[:baz] = key
```
O `:callback` e `:variable` são opções que podem ser utilizadas para o objeto
de renderização:
```javascript
var resource = {"foo":"bar","baz":"qux"};
present(resource);
```
#### WLang Templates
Dependência |
WLang
|
Extensão do Arquivo |
.wlang |
Exemplo |
wlang :index, :locals => { :key => 'value' } |
Já que você não pode chamar o Ruby (exceto pelo método
`yield`) pelo template WLang,
você quase sempre precisará passar o `locals` para ele.
## Acessando Variáveis nos Templates
Templates são avaliados dentro do mesmo contexto como manipuladores de
rota. Variáveis de instância definidas em manipuladores de rota 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 de 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 com `yield` e layouts aninhados
Um layout geralmente é apenas um template que executa `yield`.
Tal template pode ser utilizado pela opção `:template` descrita acima ou pode
ser renderizado através de um bloco, como a seguir:
```ruby
erb :post, :layout => false do
erb :index
end
```
Este código é quase equivalente a `erb :index, :layout => :post`
Passando blocos para os métodos de renderização é útil para criar layouts
aninhados:
```ruby
erb :main_layout, :layout => false do
erb :admin_layout do
erb :user
end
end
```
Também pode ser feito com menos linhas de código:
```ruby
erb :admin_layout, :layout => :main_layout do
erb :user
end
```
Atualmente os métodos listados aceitam blocos: `erb`, `haml`,
`liquid`, `slim `, `wlang`. E o método geral `render` também aceita blocos.
### Templates Inline
Templates podem ser definidos no final do arquivo fonte:
```ruby
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` explicitamente se
você tem templates inline em outros arquivos 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` ou desabilita-los por padrão
via `set :haml, :layout => false`
```ruby
get '/' do
haml :index, :layout => !request.xhr?
end
```
### Associando Extensões de Arquivos
Para associar uma extensão de arquivo com um engine de template use o método
`Tilt.register`. Por exemplo, se você quiser usar a extensão `tt` para os
templates Textile você pode fazer o seguinte:
```ruby
Tilt.register :tt, Tilt[:textile]
```
### Adicionando seu Próprio Engine de Template
Primeiro registre seu engine utilizando o Tilt, e então crie um método de
renderização:
```ruby
Tilt.register :myat, MyAwesomeTemplateEngine
helpers do
def myat(*args) render(:myat, *args) end
end
get '/' do
myat :index
end
```
Renderize `./views/index.myat`. Aprenda mais sobre o
Tilt [aqui](https://github.com/rtomayko/tilt#readme).
### Customizando Lógica para Encontrar Templates
Para implementar sua própria lógica para busca de templates você pode escrever
seu próprio método `#find_template`
```ruby
configure do
set :views [ './views/a', './views/b' ]
end
def find_template(views, name, engine, &block)
Array(views).each do |v|
super(v, name, engine, &block)
end
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 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 mesmo contexto da
requisição e também podem modificar a requisição 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
```
Nota: A não ser que você use o metódo `body` ao invés de apenas retornar uma
String das rotas, o corpo ainda não estará disponível no filtro after, uma vez
que é gerado depois.
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
authenticate!
end
after '/create/:slug' do |slug|
session[:last_slug] = slug
end
```
Como rotas, filtros também aceitam condições:
```ruby
before :agent => /Songbird/ do
#...
end
after '/blog/*', :host_name => 'exemplo.com' do
#...
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
```
Alternativamente, métodos auxiliares podem ser definidos separadamente em
módulos:
```ruby
module FooUtils
def foo(nome) "#{nome}foo" end
end
module BarUtils
def bar(nome) "#{nome}bar" end
end
helpers FooUtils, BarUtils
```
O efeito é o mesmo que incluir os módulos na classe da aplicação.
### Utilizando Sessões
Uma sessão é usada para manter o estado durante requisições. Se ativada, você
terá disponível um hash de sessão para cada sessão de usuário:
```ruby
enable :sessions
get '/' do
"value = " << session[:value].inspect
end
get '/:value' do
session['value'] = params['value']
end
```
#### Segredo Seguro da Sessão
Para melhorar a segurança, os dados da sessão no cookie são assinado com uma
segredo de sessão usando `HMAC-SHA1`. Esse segredo de sessão deve ser, de
preferência, um valor criptograficamente randômico, seguro, de um comprimento
apropriado no qual `HMAC-SHA1` é maior ou igual a 64 bytes (512 bits, 128
carecteres hexadecimais). Você será avisado para não usar uma chave secreta
menor que 32 bytes de randomicidade (256 bits, 64 caracteres hexadecimais).
Portanto, é **muito importante** que você não invente o segredo, mas use um
gerador de números aleatórios seguro para cria-lo. Humanos são extremamente
ruins em gerar números aleatórios.
Por padrão, um segredo de sessão aleatório seguro de 32 bytes é gerada para
você pelo Sinatra, mas ele mudará toda vez que você reiniciar sua aplicação. Se
você tiver múltiplas instâncias da sua aplicação e você deixar que o Sinatra
gere a chave, cada instância teria uma chave de sessão diferente, o que
certamente não é o que você quer.
Para melhor segurança e usabilidade é
[recomendado](https://12factor.net/config) que você gere um segredo randômico
secreto e salve-o em uma variável de ambiente em cada host rodando sua
aplicação, assim todas as instâncias da sua aplicação irão compartilhar o mesmo
segredo. Você deve, periodicamente, mudar esse segredo de sessão para um novo
valor. Abaixo, são mostrados alguns exemplos de como você pode criar um segredo
de 64 bytes e usa-lo:
**Gerando Segredo de Sessão**
```text
$ ruby -e "require 'securerandom'; puts SecureRandom.hex(64)"
99ae8af...snip...ec0f262ac
```
**Gerando Segredo de Sessão (Pontos adicionais)**
Use preferencialmente a
[gem sysrandom](https://github.com/cryptosphere/sysrandom#readme) para utilizar
as facilidades do sistema RNG para gerar valores aleatórios ao invés
do `OpenSSL` no qual o MRI Ruby padroniza para:
```text
$ gem install sysrandom
Building native extensions. This could take a while...
Sucessfully installed sysrandom-1.x
1 gem installed
$ ruby -e "require 'sysrandom/securerandom'; puts SecureRandom.hex(64)"
99ae8af...snip...ec0f262ac
```
**Segredo de Sessão numa Variável de Ambiente**
Defina uma variável de ambiente `SESSION_SECRET` para o Sinatra com o valor que
você gerou. Salve esse valor entre as reinicializações do seu host. Já que a
forma de fazer isso irá variar entre os sistemas operacionais, o exemplo abaixo
serve apenas para fins ilustrativos:
```bash
# echo "export SESSION_SECRET = 99ae8af...snip...ec0f262ac" >> ~/.bashrc
```
**Configurando o Segredo de Sessão na Aplicação**
Configure sua aplicação para uma falha de segredo seguro aleatório se a
variável de ambiente `SESSION_SECRET` não estiver disponível.
Como ponto adicional use a
[gem sysrandom](https://github.com/cryptosphere/sysrandom#readme) da seguinte
forma:
```ruby
require 'securerandom'
# -or- require 'sysrandom/securearandom'
set :session_secret, ENV.fecth(`SESSION_SECRET`) { SecureRandom.hex(64) }
```
#### Configuração de Sessão
Se você deseja configurar isso adicionalmente, você pode salvar um hash com
opções na definição de `sessions`:
```ruby
set :sessions, :domain => 'foo.com'
```
Para compartilhar sua sessão com outras aplicações no subdomínio de foo.com,
adicione um *.* antes do domínio como no exemplo abaixo:
```ruby
set :sessions, :domain => '.foo.com'
```
#### Escolhendo Seu Próprio Middleware de Sessão
Perceba que `enable :sessions` na verdade guarda todos seus dados num cookie.
Isto pode não ser o que você deseja sempre (armazenar muitos dados irá aumentar
seu tráfego, por exemplo). Você pode usar qualquer middleware de sessão Rack
para fazer isso, um dos seguintes métodos pode ser usado:
```ruby
enable :sessions
set :session_store, Rack::Session::Pool
```
Ou definir as sessões com um hash de opções:
```ruby
set :sessions, :expire_after => 2592000
set :session_store, Rack::Session::Pool
```
Outra opção é **não** usar `enable :sessions`, mas ao invés disso trazer seu
middleware escolhido como você faria com qualquer outro middleware.
É importante lembrar que usando esse método, a proteção baseada na sessão
**não estará habilitada por padrão**.
Para o middleware Rack fazer isso, será preciso que isso também seja adicionado:
```ruby
use Rack::Session::Pool, :expire_after => 2592000
use Rack::Protection::RemoteToken
use Rack::Protection::SessionHijacking
```
Veja '[Configurando proteção a ataques](#configurando-proteção-a-ataques)' para
mais informações.
### Parando
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 o corpo:
```ruby
halt 'isso será o corpo'
```
Ou ambos:
```ruby
halt 401, 'vá embora!'
```
Com cabeçalhos:
```ruby
halt 402, {'Content-Type' => 'text/plain'}, 'revanche'
```
Também é obviamente possível combinar um template com o `halt`:
```ruby
halt erb(:error)
```
### 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 compatível. Se nenhuma rota compatível for encontrada, um
404 é retornado.
### Desencadeando Outra Rota
As vezes o `pass` não é o que você quer, ao invés dele talvez você queira obter
o resultado chamando outra rota. Simplesmente utilize o método `call` neste
caso:
```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 que no exemplo acima você ganharia performance e facilitaria os testes ao
simplesmente mover `"bar"` para um método auxiliar usado por ambos `/foo`
e `/bar`.
Se você quer que a requisição seja enviada para a mesma instância da aplicação
no lugar de uma duplicada, use `call!` no lugar de `call`.
Veja a especificação do Rack se você quer aprender mais sobre o `call`.
### Definindo Corpo, Código de Status e Cabeçalhos
É possível e recomendado definir o código de status e o corpo da resposta com o
valor retornado do bloco da rota. Entretanto, em alguns cenários você pode
querer definir o corpo em um ponto arbitrário do fluxo de execução. Você pode
fazer isso com o metódo auxiliar `body`. Se você fizer isso, poderá usar esse
metódo de agora em diante para acessar o body:
```ruby
get '/foo' do
body "bar"
end
after do
puts body
end
```
Também é possivel passar um bloco para `body`, que será executado pelo
manipulador Rack (isso pode ser usado para implementar transmissão,
[veja "Retorno de Valores"](#retorno-de-valores)).
Similar ao corpo, você pode também definir o código de status e cabeçalhos:
```ruby
get '/foo' do
status 418
headers \
"Allow" => "BREW, POST, GET, PROPFIND, WHEN"
"Refresh" => "Refresh: 20; https://ietf.org/rfc/rfc2324.txt"
body "Eu sou um bule de chá!"
end
```
Assim como `body`, `headers` e `status` sem argumentos podem ser usados para
acessar seus valores atuais.
### Transmitindo Respostas
As vezes você quer começar a mandar dados enquanto está gerando partes do corpo
da resposta. Em exemplos extremos, você quer continuar enviando dados até o
cliente encerrar a conexão. Você pode usar o método auxiliar `stream` para
evitar criar seu próprio empacotador:
```ruby
get '/' do
stream do |out|
out << "Isso será len -\n"
sleep 0.5
out << " Aguarde \n"
sleep 1
out << " dário!\n"
end
end
```
Isso permite você implementar APIs de Transmissão,
[Eventos Enviados pelo Servidor](https://w3c.github.io/eventsource/), e pode
ser usado como a base para
[WebSockets](https://en.wikipedia.org/wiki/WebSocket). Pode ser usado também
para aumentar a taxa de transferência se algum, mas não todo, conteúdo depender
de um recurso lento.
Perceba que o comportamento da transmissão, especialmente o número de
requisições concorrentes, depende altamente do servidor web usado para servir a
aplicação. Alguns servidores podem até mesmo não suportar transmissão de
maneira alguma. Se o servidor não suportar transmissão, o corpo será enviado
completamente após que o bloco passado para `stream` terminar de executar.
Transmissão não funciona de nenhuma maneira com Shotun.
Se o parâmetro opcional é definido como `keep_open`, ele não chamará `close` no
objeto transmitido, permitindo você a fecha-lo em algum outro ponto futuro no
fluxo de execução. Isso funciona apenas em servidores orientados a eventos,
como Thin e Rainbows. Outros servidores irão continuar fechando a transmissão:
```ruby
# long polling
set :server, :thin
conexoes = []
get '/assinar' do
# registra o interesse de um cliente em servidores de eventos
stream(:keep_open) do |saida|
conexoes << saida
# retire conexões mortas
conexoes.reject!(&:closed?)
end
end
post '/:messagem' do
conexoes.each do |saida|
# notifica o cliente que uma nova mensagem chegou
saida << params['messagem'] << "\n"
# indica ao cliente para se conectar novamente
saida.close
end
# confirma
"messagem recebida"
end
```
Também é possivel para o cliente fechar a conexão quando está tentando escrever
para o socket. Devido a isso, é recomendado checar `out.closed?` antes de
tentar escrever.
### Usando Logs
No escopo da requisição, o método auxiliar `logger` expõe uma instância
`Logger`:
```ruby
get '/' do
logger.info "loading data"
# ...
end
```
Esse logger irá automaticamente botar as configurações de log do manipulador
Rack na sua conta. Se a produção de logs estiver desabilitads, esse método
retornará um objeto dummy, então você não terá que se preocupar com suas rotas
e filtros.
Perceba que a produção de logs está habilitada apenas para
`Sinatra::Application` por padrão, então se você herdar de `Sinatra::Base`,
você provavelmente irá querer habilitar:
```ruby
class MyApp < Sinatra::Base
configure :production, :development do
enable :logging
end
end
```
Para evitar que qualquer middleware de logs seja configurado, defina a
configuração `logging` como `nil`. Entretanto, tenha em mente que `logger`
retornará, nesse caso, `nil`. Um caso de uso comum é quando você quer definir
seu próprio logger. Sinatra irá usar qualquer um que ele achar
em `env['rack.logger']`
### Tipos Mime
Quando se está usando `send_file` ou arquivos estáticos, você pode ter tipos
mime que o Sinatra não entende. Use `mime_type` para registrá-los pela extensão
do arquivo:
```ruby
configure do
mime_type :foo, 'text/foo'
end
```
Você pode utilizar também com o método auxiliar `content_type`:
```ruby
get '/' do
content-type :foo
"foo foo foo"
end
```
### Gerando URLs
Para gerar URLs você deve usar o metódo auxiliar `url` no Haml:
```ruby
%a{:href => url('/foo')} foo
```
Isso inclui proxies reversos e rotas Rack, se presentes.
Esse método é também apelidado para `to` (veja
[abaixo](#redirecionamento-do-browser) para um exemplo).
### Redirecionamento do Browser
Você pode lançar um redirecionamento no browser com o metódo auxiliar
`redirect`:
```ruby
get '/foo' do
redirect to('/bar')
end
```
Quaisquer paramêtros adicionais são interpretados como argumentos passados
ao `halt`:
```ruby
redirect to('/bar'), 303
redirect 'http://www.google.com/', 'lugar errado, amigo'
```
Você pode também facilmente redirecionar para a página da qual o usuário veio
com `redirect back`:
```ruby
get '/foo' do
"do something"
end
get '/bar' do
do_something
redirect back
end
```
Para passar argumentos com um redirecionamento, adicione-os a query:
```ruby
redirect to('/bar?sum=42')
```
Ou use uma sessão:
```ruby
enable :sessions
get '/foo' do
session[:secret] = 'foo'
redirect to('/bar')
end
get '/bar' do
session[:secret]
end
```
### Controle de Cache
Definir sues cabeçalhos corretamente é o principal passo para uma correta cache
HTTP.
Você pode facilmente definir o cabeçalho de Cache-Control como:
```ruby
get '/' do
cache_control :public
"guarde isso!"
end
```
Dica profissional: Configure a cache em um filtro anterior:
```ruby
before do
cache_control :public, :must_revalidate, :max_age => 60
end
```
Se você está usando o método auxiliar `expires` para definir seu cabeçalho
correspondente, `Cache-Control` irá ser definida automaticamente para você:
```ruby
before do
expires 500, :public, :must_revalidate
end
```
Para usar propriciamente caches, você deve considerar usar `etag` ou
`last_modified`. É recomendado chamar esses métodos auxiliares *antes* de fazer
qualquer tipo de processamento pesado, já que eles irão imediatamente retornar
uma resposta se o cliente já possui a versão atual na sua cache:
```ruby
get "/artigo/:id" do
@artigo = Artigo.find params['id']
last_modified @artigo.updated_at
etag @artigo.sha1
erb :artigo
end
```
Também é possível usar uma
[ETag fraca](https://en.wikipedia.org/wiki/HTTP_ETag#Strong_and_weak_validation):
```ruby
etag @article.sha1, :weak
```
Esses métodos auxiliares não irão fazer nenhum processo de cache para você, mas
irão alimentar as informações necessárias para sua cache. Se você está
pesquisando por uma solução rápida de fazer cache com proxy-reverso, tente
[rack-cache](https://github.com/rtomayko/rack-cache#readme):
```ruby
require "rack/cache"
require "sinatra"
use Rack::Cache
get '/' do
cache_control :public, :max_age => 36000
sleep 5
"olá"
end
```
Use a configuração `:static_cache_control` (veja [acima]#(controle-de-cache))
para adicionar o cabeçalho de informação `Cache-Control` para arquivos
estáticos.
De acordo com a RFC 2616, sua aplicação deve se comportar diferentemente se o
cabeçalho If-Match ou If-None-Match é definido para `*`, dependendo se o
recurso requisitado já existe. Sinatra assume que recursos para requisições
seguras (como get) e idempotentes (como put) já existem, enquanto que para
outros recursos (por exemplo requisições post) são tratados como novos
recursos. Você pode mudar esse comportamento passando em uma opção
`:new_resource`:
```ruby
get '/create' do
etag '', :new_resource => true
Artigo.create
erb :novo_artigo
end
```
Se você quer continuar usando um ETag fraco, passe em uma opção `:kind`:
```ruby
etag '', :new_resource => true, :kind => :weak
```
### Enviando Arquivos
Para retornar os conteúdos de um arquivo como as resposta, você pode usar o
metódo auxiliar `send_file`:
```ruby
get '/' do
send_file 'foo.png'
end
```
Também aceita opções:
```ruby
send_file 'foo.png', :type => :jpg
```
As opções são:
- filename
- Nome do arquivo a ser usado na respota,
o padrão é o nome do arquivo reak
- last_modified
- Valor do cabeçalho Last-Modified, o padrão corresponde ao mtime do
arquivo.
- type
- Valor do cabeçalho Content-Type, extraído da extensão do arquivo se
inexistente.
- disposition
-
Valor do cabeçalho Content-Disposition, valores possíveis: nil
(default), :attachment and :inline
- length
- Valor do cabeçalho Content-Length, o padrão corresponde ao tamanho do
arquivo.
- status
-
Código de status a ser enviado. Útil quando está se enviando um arquivo
estático como uma página de erro. Se suportado pelo handler do Rack,
outros meios além de transmissão do processo do Ruby serão usados.
So você usar esse metódo auxiliar, o Sinatra irá automaticamente lidar com
requisições de alcance.
### Acessando o Objeto da Requisção
O objeto vindo da requisição pode ser acessado do nível de requsição (filtros,
rotas, manipuladores de erro) através do método `request`:
```ruby
# app rodando em http://exemplo.com/exemplo
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 # corpo da requisição enviado pelo cliente (veja abaixo)
request.scheme # "http"
request.script_name # "/exemplo"
request.path_info # "/foo"
request.port # 80
request.request_method # "GET"
request.query_string # ""
request.content_length # tamanho do request.body
request.media_type # tipo de mídia of request.body
request.host # "exemplo.com"
request.get? # true (metodo similar para outros tipos de requisição)
request.form_data? # false
request["algum_ param"] # valor do paramêtro 'algum_param'. [] é um atalho para o hash de parametros
request.referrer # a referência do cliente ou '/'
request.user_agent # agente de usuário (usado por :agent condition)
request.cookies # hash dos cookies do browser
request.xhr? # isto é uma requisição ajax?
request.url # "http://exemplo.com/exemplo/foo"
request.path # "/exemplo/foo"
request.ip # endereço de IP do cliente
request.secure? # false (seria true se a conexão fosse ssl)
request.forwarded? # true (se está rodando por um proxy reverso)
request.env # raw env hash handed in by Rack
end
```
Algumas opções, como `script_name` ou `path_info, podem ser escritas como:
```ruby
before { request.path_info = "/" }
get "/" do
"todas requisições acabam aqui"
end
```
`request.body` é uma ES ou um objeo StringIO:
```ruby
post "/api" do
request.body.rewind # em caso de algo já ter lido
data = JSON.parse request.body.read
"Oi #{data['nome']}!"
end
```
### Anexos
Você pode usar o método auxiliar `attachment` para dizer ao navegador que a
reposta deve ser armazenada no disco no lugar de ser exibida no browser:
```ruby
get '/' do
attachment "info.txt"
"salve isso!"
end
```
### Trabalhando com Data e Hora
O Sinatra oferece um método auxiliar `time_for` que gera um objeto Time do
valor dado. É também possível converter `DateTime`, `Date` e classes similares:
```ruby
get '/' do
pass if Time.now > time_for('Dec 23, 2016')
"continua no tempo"
end
```
Esse método é usado internamente por `expires`, `last_modified` e akin. Você
pode portanto facilmente estender o comportamento desses métodos sobrescrevendo
`time_for` na sua aplicação:
```ruby
helpers do
def time_for(valor)
case valor
when :ontem then Time.now - 24*60*60
when :amanha then Time.now + 24*60*60
else super
end
end
end
get '/' do
last_modified :ontem
expires :amanha
"oi"
end
```
### Pesquisando por Arquivos de Template
O método auxiliar `find_template` é usado para encontrar arquivos de template
para renderizar:
```ruby
find_template settings.views, 'foo', Tilt[:haml] do |arquivo|
puts "pode ser #{arquivo}"
end
```
Isso não é realmente útil. Mas é útil que você possa na verdade sobrescrever
esse método para conectar no seu próprio mecanismo de pesquisa. Por exemplo, se
você quer ser capaz de usar mais de um diretório de view:
```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
```
Outro exemplo seria utilizando diretórios diferentes para motores (engines)
diferentes:
```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
```
Você pode facilmente embrulhar isso é uma extensão e compartilhar com outras
pessoas!
Perceba que `find_template` não verifica se o arquivo realmente existe. Ao
invés disso, ele chama o bloco dado para todos os caminhos possíveis. Isso não
significa um problema de perfomance, já que `render` irá usar `break` assim que
o arquivo é encontrado. Além disso, as localizações (e conteúdo) de templates
serão guardados na cache se você não estiver rodando no modo de
desenvolvimento. Você deve se lembrar disso se você escrever um método
realmente maluco.
## Configuração
É possível e recomendado definir o código de status e o corpo da resposta com o
valor retornado do bloco da rota. Entretanto, em alguns cenários você pode
querer definir o corpo em um ponto arbitrário do fluxo de execução. Você pode
fazer isso com o metódo auxiliar `body`. Se você fizer isso, poderá usar esse
metódo de agora em diante para acessar o body:
```ruby
get '/foo' do
body "bar"
end
after do
puts body
end
```
Também é possivel passar um bloco para `body`, que será executado pelo
manipulador Rack (isso pode ser usado para implementar transmissão,
[veja "Retorno de Valores"](#retorno-de-valores)).
Similar ao corpo, você pode também definir o código de status e cabeçalhos:
```ruby
get '/foo' do
status 418
headers \
"Allow" => "BREW, POST, GET, PROPFIND, WHEN"
"Refresh" => "Refresh: 20; https://ietf.org/rfc/rfc2324.txt"
body "Eu sou um bule de chá!"
end
```
Assim como `body`, `headers` e `status` sem argumentos podem ser usados para
acessar seus valores atuais.
### Transmitindo Respostas
As vezes você quer começar a mandar dados enquanto está gerando partes do corpo
da resposta. Em exemplos extremos, você quer continuar enviando dados até o
cliente encerrar a conexão. Você pode usar o método auxiliar `stream` para
evitar criar seu próprio empacotador:
```ruby
get '/' do
stream do |out|
out << "Isso será len -\n"
sleep 0.5
out << " Aguarde \n"
sleep 1
out << " dário!\n"
end
end
```
Isso permite você implementar APIs de Transmissão,
[Eventos Enviados pelo Servidor](https://w3c.github.io/eventsource/), e pode
ser usado como a base para
[WebSockets](https://en.wikipedia.org/wiki/WebSocket). Pode ser usado também
para aumentar a taxa de transferência se algum, mas não todo, conteúdo depender
de um recurso lento.
Perceba que o comportamento da transmissão, especialmente o número de
requisições concorrentes, depende altamente do servidor web usado para servir a
aplicação. Alguns servidores podem até mesmo não suportar transmissão de
maneira alguma. Se o servidor não suportar transmissão, o corpo será enviado
completamente após que o bloco passado para `stream` terminar de executar.
Transmissão não funciona de nenhuma maneira com Shotun.
Se o parâmetro opcional é definido como `keep_open`, ele não chamará `close` no
objeto transmitido, permitindo você a fecha-lo em algum outro ponto futuro no
fluxo de execução. Isso funciona apenas em servidores orientados a eventos,
como Thin e Rainbows. Outros servidores irão continuar fechando a transmissão:
```ruby
# long polling
set :server, :thin
conexoes = []
get '/assinar' do
# registra o interesse de um cliente em servidores de eventos
stream(:keep_open) do |saida|
conexoes << saida
# retire conexões mortas
conexoes.reject!(&:closed?)
end
end
post '/:messagem' do
conexoes.each do |saida|
# notifica o cliente que uma nova mensagem chegou
saida << params['messagem'] << "\n"
# indica ao cliente para se conectar novamente
saida.close
end
# confirma
"messagem recebida"
end
```
Também é possivel para o cliente fechar a conexão quando está tentando escrever
para o socket. Devido a isso, é recomendado checar `out.closed?` antes de
tentar escrever.
### Usando Logs
No escopo da requisição, o método auxiliar `logger` expõe uma instância
`Logger`:
```ruby
get '/' do
logger.info "loading data"
# ...
end
```
Esse logger irá automaticamente botar as configurações de log do manipulador
Rack na sua conta. Se a produção de logs estiver desabilitads, esse método
retornará um objeto dummy, então você não terá que se preocupar com suas rotas
e filtros.
Perceba que a produção de logs está habilitada apenas para
`Sinatra::Application` por padrão, então se você herdar de `Sinatra::Base`,
você provavelmente irá querer habilitar:
```ruby
class MyApp < Sinatra::Base
configure :production, :development do
enable :logging
end
end
```
Para evitar que qualquer middleware de logs seja configurado, defina a
configuração `logging` como `nil`. Entretanto, tenha em mente que `logger`
retornará, nesse caso, `nil`. Um caso de uso comum é quando você quer definir
seu próprio logger. Sinatra irá usar qualquer um que ele achar em
`env['rack.logger']`
### Tipos Mime
Quando se está usando `send_file` ou arquivos estáticos, você pode ter tipos
Mime que o Sinatra não entende. Use `mime_type` para registrá-los pela extensão
do arquivo:
```ruby
configure do
mime_type :foo, 'text/foo'
end
```
Você pode utilizar também com o método auxiliar `content_type`:
```ruby
get '/' do
content-type :foo
"foo foo foo"
end
```
### Gerando URLs
Para gerar URLs você deve usar o metódo auxiliar `url` no Haml:
```ruby
%a{:href => url('/foo')} foo
```
Isso inclui proxies reversos e rotas Rack, se presentes.
Esse método é também apelidado para `to` (veja
[abaixo](#redirecionamento-do-browser) para um exemplo).
### Redirecionamento do Browser
Você pode lançar um redirecionamento no browser com o metódo auxiliar
`redirect`:
```ruby
get '/foo' do
redirect to('/bar')
end
```
Quaisquer paramêtros adicionais são interpretados como argumentos passados ao
`halt`:
```ruby
redirect to('/bar'), 303
redirect 'http://www.google.com/', 'lugar errado, amigo'
```
Você pode também facilmente redirecionar para a página da qual o usuário veio
com `redirect back`:
```ruby
get '/foo' do
"do something"
end
get '/bar' do
do_something
redirect back
end
```
Para passar argumentos com um redirecionamento, adicione-os a query:
```ruby
redirect to('/bar?sum=42')
```
Ou use uma sessão:
```ruby
enable :sessions
get '/foo' do
session[:secret] = 'foo'
redirect to('/bar')
end
get '/bar' do
session[:secret]
end
```
### Controle de Cache
Definir sues cabeçalhos corretamente é o principal passo para uma correta cache
HTTP.
Você pode facilmente definir o cabeçalho de Cache-Control como:
```ruby
get '/' do
cache_control :public
"guarde isso!"
end
```
Dica profissional: Configure a cache em um filtro anterior:
```ruby
before do
cache_control :public, :must_revalidate, :max_age => 60
end
```
Se você está usando o método auxiliar `expires` para definir seu cabeçalho
correspondente, `Cache-Control` irá ser definida automaticamente para você:
```ruby
before do
expires 500, :public, :must_revalidate
end
```
Para usar propriciamente caches, você deve considerar usar `etag` ou
`last_modified`. É recomendado chamar esses métodos auxiliares *antes* de fazer
qualquer tipo de processamento pesado, já que eles irão imediatamente retornar
uma resposta se o cliente já possui a versão atual na sua cache:
```ruby
get "/artigo/:id" do
@artigo = Artigo.find params['id']
last_modified @artigo.updated_at
etag @artigo.sha1
erb :artigo
end
```
Também é possível usar uma
[ETag fraca](https://en.wikipedia.org/wiki/HTTP_ETag#Strong_and_weak_validation):
```ruby
etag @article.sha1, :weak
```
Esses métodos auxiliares não irão fazer nenhum processo de cache para você, mas
irão alimentar as informações necessárias para sua cache. Se você está
pesquisando por uma solução rápida de fazer cache com proxy-reverso, tente
[rack-cache](https://github.com/rtomayko/rack-cache#readme):
```ruby
require "rack/cache"
require "sinatra"
use Rack::Cache
get '/' do
cache_control :public, :max_age => 36000
sleep 5
"olá"
end
```
Use a configuração `:static_cache_control` (veja [acima]#(controle-de-cache))
para adicionar o cabeçalho de informação `Cache-Control` para arquivos
estáticos.
De acordo com a RFC 2616, sua aplicação deve se comportar diferentemente se o
cabeçalho If-Match ou If-None-Match é definido para `*`, dependendo se o
recurso requisitado já existe. Sinatra assume que recursos para requisições
seguras (como get) e idempotentes (como put) já existem, enquanto que para
outros recursos (por exemplo requisições post) são tratados como novos
recursos. Você pode mudar esse comportamento passando em uma opção
`:new_resource`:
```ruby
get '/create' do
etag '', :new_resource => true
Artigo.create
erb :novo_artigo
end
```
Se você quer continuar usando um ETag fraco, passe em uma opção `:kind`:
```ruby
etag '', :new_resource => true, :kind => :weak
```
### Enviando Arquivos
Para retornar os conteúdos de um arquivo como as resposta, você pode usar o
metódo auxiliar `send_file`:
```ruby
get '/' do
send_file 'foo.png'
end
```
Também aceita opções:
```ruby
send_file 'foo.png', :type => :jpg
```
As opções são:
- filename
- Nome do arquivo a ser usado na respota,
o padrão é o nome do arquivo reak
- last_modified
- Valor do cabeçalho Last-Modified, o padrão corresponde ao mtime do
arquivo.
- type
- Valor do cabeçalho Content-Type, extraído da extensão do arquivo se
inexistente.
- disposition
-
Valor do cabeçalho Content-Disposition, valores possíveis: nil
(default), :attachment and :inline
- length
- Valor do cabeçalho Content-Length, o padrão corresponde ao tamanho do
arquivo.
- status
-
Código de status a ser enviado. Útil quando está se enviando um arquivo
estático como uma página de erro. Se suportado pelo handler do Rack,
outros meios além de transmissão do processo do Ruby serão usados.
So você usar esse metódo auxiliar, o Sinatra irá automaticamente lidar
com requisições de alcance.
### Acessando o Objeto da Requisção
O objeto vindo da requisição pode ser acessado do nível de requsição (filtros,
rotas, manipuladores de erro) através do método `request`:
```ruby
# app rodando em http://exemplo.com/exemplo
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 # corpo da requisição enviado pelo cliente (veja abaixo)
request.scheme # "http"
request.script_name # "/exemplo"
request.path_info # "/foo"
request.port # 80
request.request_method # "GET"
request.query_string # ""
request.content_length # tamanho do request.body
request.media_type # tipo de mídia of request.body
request.host # "exemplo.com"
request.get? # true (metodo similar para outros tipos de requisição)
request.form_data? # false
request["algum_ param"] # valor do paramêtro 'algum_param'. [] é um atalho para o hash de parametros
request.referrer # a referência do cliente ou '/'
request.user_agent # agente de usuário (usado por :agent condition)
request.cookies # hash dos cookies do browser
request.xhr? # isto é uma requisição ajax?
request.url # "http://exemplo.com/exemplo/foo"
request.path # "/exemplo/foo"
request.ip # endereço de IP do cliente
request.secure? # false (seria true se a conexão fosse ssl)
request.forwarded? # true (se está rodando por um proxy reverso)
request.env # raw env hash handed in by Rack
end
```
Algumas opções, como `script_name` ou `path_info, podem ser escritas como:
```ruby
before { request.path_info = "/" }
get "/" do
"todas requisições acabam aqui"
end
```
`request.body` é uma ES ou um objeo StringIO:
```ruby
post "/api" do
request.body.rewind # em caso de algo já ter lido
data = JSON.parse request.body.read
"Oi #{data['nome']}!"
end
```
### Anexos
Você pode usar o método auxiliar `attachment` para dizer ao navegador que a
reposta deve ser armazenada no disco no lugar de ser exibida no browser:
```ruby
get '/' do
attachment "info.txt"
"salve isso!"
end
```
### Trabalhando com Data e Hora
O Sinatra oferece um método auxiliar `time_for` que gera um objeto Time do
valor dado. É também possível converter `DateTime`, `Date` e classes similares:
```ruby
get '/' do
pass if Time.now > time_for('Dec 23, 2016')
"continua no tempo"
end
```
Esse método é usado internamente por `expires`, `last_modified` e akin. Você
pode portanto facilmente estender o comportamento desses métodos sobrescrevendo
`time_for` na sua aplicação:
```ruby
helpers do
def time_for(valor)
case valor
when :ontem then Time.now - 24*60*60
when :amanha then Time.now + 24*60*60
else super
end
end
end
get '/' do
last_modified :ontem
expires :amanha
"oi"
end
```
### Pesquisando por Arquivos de Template
O método auxiliar `find_template` é usado para encontrar arquivos de template
para renderizar:
```ruby
find_template settings.views, 'foo', Tilt[:haml] do |arquivo|
puts "pode ser #{arquivo}"
end
```
Isso não é realmente útil. Mas é útil que você possa na verdade sobrescrever
esse método para conectar no seu próprio mecanismo de pesquisa. Por exemplo, se
você quer ser capaz de usar mais de um diretório de view:
```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
```
Outro exemplo seria utilizando diretórios diferentes para motores (engines)
diferentes:
```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
```
Você pode facilmente embrulhar isso é uma extensão e compartilhar com outras
pessoas!
Perceba que `find_template` não verifica se o arquivo realmente existe. Ao
invés disso, ele chama o bloco dado para todos os caminhos possíveis. Isso não
significa um problema de perfomance, já que `render` irá usar `break` assim que
o arquivo é encontrado. Além disso, as localizações (e conteúdo) de templates
serão guardados na cache se você não estiver rodando no modo de
desenvolvimento. Você deve se lembrar disso se você escrever um método
realmente maluco.
## Configuração
Rode uma vez, na inicialização, em qualquer ambiente:
```ruby
configure do
...
end
```
```ruby
configure do
# configurando uma opção
set :option, 'value'
# configurando múltiplas opções
set :a => 1, :b => 2
# o mesmo que `set :option, true`
enable :option
# o mesmo que `set :option, false`
disable :option
# você pode também ter configurações dinâmicas com blocos
set(:css_dir) { File.join(views, 'css') }
end
```
Rode somente quando o ambiente (`APP_ENV` variável de ambiente) é definida para
`:production`:
```ruby
configure :production do
...
end
```
Rode quando o ambiente é definido para `:production` ou `:test`:
```ruby
configure :production, :test do
...
end
```
Você pode acessar essas opções por meio de `settings`:
```ruby
configure do
set :foo, 'bar'
end
get '/' do
settings.foo? # => true
settings.foo # => 'bar'
...
end
```
### Configurando proteção a ataques
O Sinatra está usando
[Rack::Protection](https://github.com/sinatra/sinatra/tree/master/rack-protection#readme)
para defender sua aplicação contra ataques oportunistas comuns. Você pode
facilmente desabilitar esse comportamento (o que irá abrir sua aplicação à
toneladas de vulnerabilidades comuns):
```ruby
disable :protection
```
Para pular uma única camada de defesa, defina `protection` como um hash de
opções:
```ruby
set :protection, :except => :path_traversal
```
Você também pode definir em um array, visando desabilitar uma lista de
proteções:
```ruby
set :protection, :except => [:path_traversal, :session_hijacking]
```
Por padrão, o Sinatra irá configurar apenas sessões com proteção se `:sessions`
tiver sido habilitado. Veja '[Utilizando Sessões](#utilizando-sessões)'. As
vezes você pode querer configurar sessões "fora" da aplicação do Sinatra, como
em config.ru ou com uma instância de `Rack::Builder` separada. Nesse caso, você
pode continuar configurando uma sessão com proteção passando a opção `:session`:
```ruby
set :protection, :session => true
```
### Configurações Disponíveis
- absolute_redirects
-
Se desabilitada, o Sinatra irá permitir redirecionamentos relativos,
entretanto, isso não estará conforme a RFC 2616 (HTTP 1.1), que permite
apenas redirecionamentos absolutos.
-
Habilite se sua aplicação estiver rodando antes de um proxy reverso que
não foi configurado corretamente. Note que o método auxiliar url
irá continuar produzindo URLs absolutas, a não ser que você passe
false como segundo parâmetro.
- Desabilitado por padrão.
- add_charset
-
Para tipos Mime o método auxiliar content_type irá
automaticamente adicionar a informção de codificação. Você deve adcionar
isto no lugar de sobrescrever essa opção:
settings.add_charset << "application/foobar"
- app_file
-
Caminho para o arquivo principal da aplicação, usado para detectar a raíz
do projeto, views e pastas públicas e templates inline.
- bind
-
Endereço IP a ser ligado (padrão: 0.0.0.0 ou localhost
se seu ambiente está definido como desenvolvimento). Usado apenas para
servidor embutido.
- default_encoding
- Codificação assumida caso a mesma seja desconhecida (padrão corresponde
a "utf-8").
- dump_errors
- Exibe erros no log.
- environment
-
Ambiente atual. O padrão é ENV['APP_ENV'], ou
"development" se o primeiro não estiver disponível.
- logging
- Usa o logger.
- lock
-
Coloca um bloqueio em torno de cada requisição, executando apenas
processamento sob requisição por processo Ruby simultaneamente.
- Habilitado se sua aplicação não for 'thread-safe'. Desabilitado por
padrão.
- method_override
-
Use a mágica _method para permitir formulários put/delete em
navegadores que não oferecem suporte à essas operações.
- mustermann_opts
-
Um hash de opções padrão para passar a Mustermann.new quando se está
compilado os caminho de roteamento.
- port
- Porta a ser escutada. Usado apenas para servidores embutidos.
- prefixed_redirects
-
Inserir ou não inserir request.script_name nos redirecionamentos
se nenhum caminho absoluto for dado. Dessa forma redirect '/foo'
irá se comportar como redirect to('/foo').
- Desabilitado por padrão.
- protection
-
Habilitar ou não proteções a ataques web. Veja a sessão de proteção acima.
- public_dir
- Apelido para public_folder. Veja abaixo.
- public_folder
-
Caminho para o diretório de arquivos públicos. Usado apenas se a exibição
de arquivos estáticos estiver habilitada (veja a configuração
static abaixo). Deduzido da configuração app_file se
não for definido.
- quiet
-
Desabilita logs gerados pelos comandos de inicio e parada do Sinatra.
false por padrão.
- reload_templates
-
Se deve ou não recarregar templates entre as requisições. Habilitado no
modo de desenvolvimento.
- root
-
Caminho para o diretório raíz do projeto. Deduzido da configuração
app_file se não for definido.
- raise_errors
-
Lança exceções (irá para a aplicação). Habilitado por padrão quando o
ambiente está definido para "test, desabilitado em caso
contrário.
- run
-
Se habilitado, o Sinatra irá lidar com o início do servidor web. Não
habilite se estiver usando rackup ou outros meios.
- running
- É o servidor embutido que está rodando agora? Não mude essa
configuração!
- server
-
Servidor ou listas de servidores para usar o servidor embutido. A ordem
indica prioridade, por padrão depende da implementação do Ruby
- server_settings
-
Se você estiver usando um servidor web WEBrick, presumidamente para seu
ambiente de desenvolvimento, você pode passar um hash de opções para
server_settings, tais como SSLEnable ou
SSLVerifyClient. Entretanto, servidores web como Puma e Thin não
suportam isso, então você pode definir server_settings como um
metódo quando chamar configure.
- sessions
-
Habilita o suporte a sessões baseadas em cookie usando
Rack::Session::Cookie. Veja a seção 'Usando Sessões' para mais
informações.
- session_store
-
O middleware de sessão Rack usado. O padrão é
Rack::Session::Cookie. Veja a sessão 'Usando Sessões' para mais
informações.
- show_exceptions
-
Mostra um relatório de erros no navegador quando uma exceção ocorrer.
Habilitado por padrão quando o ambiente é definido como
"development", desabilitado caso contrário.
-
Pode também ser definido para :after_handler para disparar um
manipulador de erro específico da aplicação antes de mostrar um relatório
de erros no navagador.
- static
-
Define se o Sinatra deve lidar com o oferecimento de arquivos
estáticos.
-
Desabilitado quando está utilizando um servidor capaz de fazer isso
sozinho.
- Desabilitar irá aumentar a perfomance
-
Habilitado por padrão no estilo clássico, desabilitado para aplicações
modulares.
- static_cache_control
-
Quando o Sinatra está oferecendo arquivos estáticos, definir isso irá
adicionar cabeçalhos Cache-Control nas respostas. Usa o método
auxiliar cache-control. Desabilitado por padrão.
-
Use um array explícito quando estiver definindo múltiplos valores:
set :static_cache_control, [:public, :max_age => 300]
- threaded
-
Se estiver definido como true, irá definir que o Thin use
EventMachine.defer para processar a requisição.
- traps
- Define se o Sinatra deve lidar com sinais do sistema.
- views
-
Caminho para o diretório de views. Deduzido da configuração
app_file se não estiver definido.
- x_cascade
-
Se deve ou não definir o cabeçalho X-Cascade se nenhuma rota combinar.
Definido como padrão true
## Ambientes
Existem três `environments` (ambientes) pré-definidos: `"development"`
(desenvolvimento), `"production"` (produção) e `"test"` (teste). Ambientes
podem ser definidos através da variável de ambiente `APP_ENV`. O valor padrão é
`"development"`. No ambiente `"development"` todos os templates são
recarregados entre as requisições e manipuladores especiais como `not_found` e
`error` exibem relatórios de erros no seu navegador. Nos ambientes de
`"production"` e `"test"`, os templates são guardos em cache por padrão.
Para rodar diferentes ambientes, defina a variável de ambiente `APP_ENV`:
```shell
APP_ENV=production ruby minha_app.rb
```
Você pode usar métodos pré-definidos: `development?`, `test?` e `production?`
para checar a configuração atual de ambiente:
```ruby
get '/' do
if settings.development?
"desenvolvimento!"
else
"não está em desenvolvimento!"
end
end
```
## Tratamento de Erros
Manipuladores de erros rodam dentro do mesmo contexto como rotas e filtros
before, o que significa que você pega todos os "presentes" que eles têm para
oferecer, como `haml`, `erb`, `halt`, etc.
### Não Encontrado
Quando uma exceção `Sinatra::NotFound` é lançada, 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 toda a vez que uma exceção é lançada a
partir de um bloco de rota ou um filtro. Note que em desenvolvimento, ele irá
rodar apenas se você tiver definido a opção para exibir exceções em
`:after_handler`:
```ruby
set :show_exceptions, :after_handler
```
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'].message
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 um manipulador de erro para um código
de status:
```ruby
error 403 do
'Accesso negado'
end
get '/secreto' do
403
end
```
Ou um alcance:
```ruby
error 400..510 do
'Boom'
end
```
O Sinatra instala os manipuladores especiais `not_found` e `error`
quando roda sobre o ambiente de desenvolvimento para exibir relatórios de erros
bonitos e informações adicionais de "debug" no seu navegador.
## Rack Middleware
O Sinatra roda no [Rack](http://rack.github.io/), 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://www.rubydoc.info/github/rack/rack/master/Rack/Builder)
(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.
Você pode achar middlwares utéis em
[rack](https://github.com/rack/rack/tree/master/lib/rack),
[rack-contrib](https://github.com/rack/rack-contrib#readme),
ou em [Rack wiki](https://github.com/rack/rack/wiki/List-of-Middleware).
## 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 'minitest/autorun'
require 'rack/test'
class MinhaAplicacaoTeste < Minitest::Test
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: Se você está usando o Sinatra no estilo modular, substitua
`Sinatra::Application' acima com o nome da classe da sua aplicação
## 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
```
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`; caso
contrário, todos os métodos DSL do Sinatra são importados para o espaço de
nomes principal.
* Coloque as rotas da sua aplicação, manipuladores de erro, filtros e opções
numa subclasse de `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://www.sinatrarb.com/configuration.html) para
detalhes de opções disponíveis e seus comportamentos. Se você quer
comportamento mais similiar à quando você definiu sua aplicação em nível mais
alto (também conhecido como estilo Clássico), você pode usar subclasses de
`Sinatra::Application`:
```ruby
require 'sinatra/base'
class MinhaApp < Sinatra::Application
get '/' do
'Olá mundo!'
end
end
```
### Estilo Clássico vs. Modular
Ao contrário da crença comum, não há nada de errado com o estilo clássico. Se
encaixa com sua aplicação, você não tem que mudar para uma aplicação modular.
As desvantagens principais de usar o estilo clássico no lugar do estilo modular
é que você ira ter apenas uma aplicação Sinatra por processo Ruby. Se seu plano
é usar mais de uma, mude para o estilo modular. Não há nenhum impedimento para
você misturar os estilos clássico e modular.
Se vai mudar de um estilo para outro, você deve tomar cuidado com algumas
configurações diferentes:
Configuração |
Clássico |
Modular |
Modular |
app_file |
arquivo carregando sinatra |
arquivo usando subclasse Sinatra::Base |
arquivo usando subclasse Sinatra::Application |
run |
$0 == app_file |
false |
false |
logging |
true |
false |
true |
method_override |
true |
false |
true |
inline_templates |
true |
false |
true |
static |
true |
File.exist?(public_folder) |
true |
### Servindo uma Aplicação Modular:
Existem duas opções comuns para começar uma aplicação modular, ativamente
começando com `run!`:
```ruby
# minha_app.rb
require 'sinatra/base'
class MinhaApp < Sinatra::Base
# ... código da aplicação aqui ...
# inicie o servidor se o arquivo ruby foi executado diretamente
run! if app_file == $0
end
```
Inicie com with:
```shell
ruby minha_app.rb
```
Ou com um arquivo `config.ru`, que permite você usar qualquer manipulador Rack:
```ruby
# config.ru (roda com rackup)
require './minha_app'
run MinhaApp
```
Rode:
```shell
rackup -p 4567
```
### Usando uma Aplicação de Estilo Clássico com um config.ru:
Escreva o arquivo da sua aplicação:
```ruby
# app.rb
require 'sinatra'
get '/' do
'Olá mundo!'
end
```
E um `config.ru` correspondente:
```ruby
require './app'
run Sinatra::Application
```
### Quando usar um config.ru?
Um arquivo `config.ru` é recomendado se:
* Você quer lançar com um manipulador Rack diferente (Passenger, Unicorn,
Heroku, ...).
* Você quer usar mais de uma subclasse de `Sinatra::Base`.
* Você quer usar Sinatra apenas como middleware, mas não como um "endpoint".
**Não há necessidade de mudar para um `config.ru` simplesmente porque você mudou para o estilo modular, e você não tem que usar o estilo modular para rodar com um `config.ru`.**
### Usando Sinatra como Middleware
O Sinatra não é capaz apenas de usar outro middleware Rack, qualquer aplicação
Sinatra pode ser adicionada na frente de qualquer "endpoint" Rack como
middleware. Esse endpoint pode ser outra aplicação Sinatra, ou qualquer outra
aplicação baseada em Rack (Rails/Hanami/Roda/...):
```ruby
require 'sinatra/base'
class TelaLogin < Sinatra::Base
enable :sessions
get('/login') { haml :login }
post('/login') do
if params['nome'] == 'admin' && params['senha'] == 'admin'
session['nome_usuario'] = params['nome']
else
redirect '/login'
end
end
end
class MinhaApp < Sinatra::Base
# middleware irá rodar filtros before
use TelaLogin
before do
unless session['nome_usuario']
halt "Acesso negado, por favor login."
end
end
get('/') { "Olá #{session['nome_usuario']}." }
end
```
### Criação de Aplicações Dinâmicas
Às vezes, você quer criar novas aplicações em tempo de execução sem ter que
associa-las a uma constante. Você pode fazer isso com `Sinatra.new`:
```ruby
require 'sinatra/base'
minha_app = Sinatra.new { get('/') { "oi" } }
minha_app.run!
```
Isso leva a aplicação à herdar como um argumento opcional:
```ruby
# config.ru (roda com rackup)
require 'sinatra/base'
controller = Sinatra.new do
enable :logging
helpers MeusHelpers
end
map('/a') do
run Sinatra.new(controller) { get('/') { 'a' } }
end
map('/b') do
run Sinatra.new(controller) { get('/') { 'b' } }
end
```
Isso é especialmente útil para testar extensões do Sinatra ou usar Sinatra na
sua própria biblioteca.
Isso também faz o uso do Sinatra como middleware extremamente fácil:
```ruby
require 'sinatra/base'
use Sinatra do
get('/') { ... }
end
run RailsProject::Application
```
# Escopos e Ligação
O escopo que você está atualmente determina quais métodos e varáveis está
disponíveis.
### Escopo de Aplicação/Classe
Toda aplicação Sinatra corresponde à um subclasse de `Sinatra::Base`. Se você
está utilizando a DSL de nível mais alto (`require 'sinatra`), então esta
classe é `Sinatra::Application`, caso contrário é a subclasse que você criou
explicitamente. No nível de classe você tem métodos como `get` ou `before`, mas
você não pode acessar os objetos `request` ou `session`, como existe apenas uma
única classe de aplicativo para todas as solicitações.
Opções criadas via `set` são métodos a nível de classe:
```ruby
class MinhaApp < Sinatra::Base
# Hey, eu estou no escopo da aplicação!
set :foo, 42
foo # => 42
get '/foo' do
# Hey, eu não estou mais no escopo da aplicação!
end
end
```
Você tem a ligação ao escopo da aplicação dentro:
* Do corpo da classe da sua aplicação
* De métodos definidos por extensões
* Do bloco passado a `helpers`
* De Procs/Blocos usados como valor para `set``
* Do bloco passado a `Sinatra.new``
Você pode atingir o escopo do objeto (a classe) de duas maneiras:
* Por meio do objeto passado aos blocos "configure" (`configure { |c| ...}`)
* `settings` de dentro do escopo da requisição
### Escopo de Instância/Requisição
Para toda requsição que chega, uma nova instância da classe da sua aplicação é
criada e todos blocos de manipulação rodam nesse escopo. Dentro desse escopo
você pode acessar os objetos `request` e `session` ou chamar métodos de
renderização como `erb` ou `haml`. Você pode acessar o escopo da aplicação de
dentro do escopo da requisição através do método auxiliar `settings`:
```ruby
class MinhaApp < Sinatra::Base
# Hey, eu estou no escopo da aplicação!
get '/define_rota/:nome' do
# Escopo da requisição para '/define_rota/:nome'
@valor = 42
settings.get("/#{params['nome']}") do
# Escopo da requisição para "/#{params['nome']}"
@valor # => nil (não é a mesma requisição)
end
"Rota definida!"
end
end
```
Você tem a ligação ao escopo da requisição dentro dos:
* blocos get, head, post, put, delete, options, patch, link e unlink
* filtros after e before
* métodos "helper" (auxiliares)
* templates/views
### Escopo de Delegação
O escopo de delegação apenas encaminha métodos ao escopo da classe. Entretando,
ele não se comporta exatamente como o escopo da classse já que você não tem a
ligação da classe. Apenas métodos marcados explicitamente para delegação
estarão disponíveis e você não compartilha variáveis/estado com o escopo da
classe (leia: você tem um `self` diferente). Você pode explicitamente adicionar
delegações de métodos chamando `Sinatra::Delegator.delegate :method_name`.
Você tem a ligação com o escopo delegado dentro:
* Da ligação de maior nível, se você digitou `require "sinatra"`
* De um objeto estendido com o mixin `Sinatra::Delegator`
Dê uma olhada no código você mesmo: aqui está [Sinatra::Delegator mixin](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/base.rb#L1609-1633) em [estendendo o objeto principal](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/main.rb#L28-30).
## Linha de Comando
Aplicações Sinatra podem ser executadas diretamente:
```shell
ruby minhaapp.rb [-h] [-x] [-q] [-e AMBIENTE] [-p PORTA] [-o HOST] [-s MANIPULADOR]
```
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 mutex (padrão é desligado)
```
### Multi-threading
_Parafraseando [esta resposta no StackOverflow](resposta-so) por Konstantin_
Sinatra não impõe nenhum modelo de concorrencia, mas deixa isso como
responsabilidade do Rack (servidor) subjacente como o Thin, Puma ou WEBrick.
Sinatra por si só é thread-safe, então não há nenhum problema se um Rack
handler usar um modelo de thread de concorrência. Isso significaria que ao
iniciar o servidor, você teria que espeficiar o método de invocação correto
para o Rack handler específico. Os seguintes exemplos é uma demonstração de
como iniciar um servidor Thin multi-thread:
```ruby
# app.rb
require 'sinatra/base'
class App < Sinatra::Base
get '/' do
'Olá mundo'
end
end
App.run!
```
Para iniciar o servidor seria:
```shell
thin --threaded start
```
## Requerimentos
As seguintes versões do Ruby são oficialmente suportadas:
- Ruby 2.2
-
2.2 é totalmente suportada e recomendada. Atualmente não existem planos
para para o suporte oficial para ela.
- Rubinius
-
Rubinius é oficialmente suportado (Rubinius >= 2.x). É recomendado rodar
gem install puma.
- JRuby
-
A útlima versão estável lançada do JRuby é oficialmente suportada. Não é
recomendado usar extensões em C com o JRuby. É recomendado rodar
gem install trinidad.
Versões do Ruby antes da 2.2.2 não são mais suportadas pelo Sinatra 2.0.
Nós também estamos de olhos em versões futuras do Ruby.
As seguintes implementações do Ruby não são oficialmente suportadas mas sabemos
que rodam o Sinatra:
* Versões antigas do JRuby e Rubinius
* Ruby Enterprise Edition
* MacRuby, Maglev, IronRuby
* Ruby 1.9.0 e 1.9.1 (mas nós não recomendamos o uso dessas)
Não ser oficialmente suportada significa que se algo quebrar e não estiver nas
plataformas suporta, iremos assumir que não é um problema nosso e sim das
plataformas.
Nós também rodas nossa IC sobre ruby-head (lançamentos futuros do MRI), mas nós
não podemos garantir nada, já que está em constante mudança. Espera-se que
lançamentos futuros da versão 2.x sejam totalmente suportadas.
Sinatra deve funcionar em qualquer sistema operacional suportado pela
implementação Ruby escolhida.
Se você rodar MacRuby, você deve rodar `gem install control_tower`.
O Sinatra atualmente não roda em Cardinal, SmallRuby, BlueRuby ou qualquer
versão do Ruby anterior ao 2.2.
## A última versão
Se você gostaria de utilizar o código da última versão do Sinatra, sinta-se
livre para rodar a aplicação com o ramo master, ele deve ser estável.
Nós também lançamos pré-lançamentos de gems de tempos em tempos, então você
pode fazer:
```shell
gem install sinatra --pre
```
para obter alguma das últimas funcionalidades.
### Com Bundler
Se você quer rodar sua aplicação com a última versão do Sinatra usando
[Bundler](https://bundler.io) é recomendado fazer dessa forma.
Primeiramente, instale o Bundler, se você ainda não tiver:
```shell
gem install bundler
```
Então, no diretório do seu projeto, crie uma `Gemfile`:
```ruby
source 'https://rubygems.org'
gem 'sinatra', :github => 'sinatra/sinatra'
# outras dependências
gem 'haml' # por exemplo, se você usar haml
```
Perceba que você terá que listar todas suas dependências de aplicação no
`Gemfile`. As dependências diretas do Sinatra (Rack e Tilt) irão, entretanto,
ser automaticamente recuperadas e adicionadas pelo Bundler.
Então você pode rodar sua aplicação assim:
```shell
bundle exec ruby myapp.rb
```
## Versionando
O Sinatras segue [Versionamento Semântico](https://semver.org/), tanto SemVer
como SemVerTag.
## 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 Problemas](https://github.com/sinatra/sinatra/issues)
* [Twitter](https://twitter.com/sinatra)
* [Lista de Email](http://groups.google.com/group/sinatrarb/topics)
* [Sinatra & Amigos](https://sinatrarb.slack.com) no Slack
([consiga um convite](https://sinatra-slack.herokuapp.com/))
* [Sinatra Book](https://github.com/sinatra/sinatra-book/) - Livro de "Receitas"
* [Sinatra Recipes](http://recipes.sinatrarb.com/) - "Receitas" de
contribuições da comunidade
* Documentação da API para a
[última release](http://www.rubydoc.info/gems/sinatra)
ou para o [HEAD atual](http://www.rubydoc.info/github/sinatra/sinatra)
no [Ruby Doc](http://www.rubydoc.info/)
* [Servidor de CI](https://travis-ci.org/sinatra/sinatra)
sinatra-2.0.8.1/README.pt-pt.md 0000664 0000000 0000000 00000042451 13603175240 0015652 0 ustar 00root root 0000000 0000000 # Sinatra
*Atenção: Este documento é apenas uma tradução da versão em inglês e
pode estar desatualizado.*
Sinatra é uma
[DSL](https://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: [http://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 /\/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.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/documentation/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 (`APP_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'].message
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.github.io/), 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://www.rubydoc.info/github/rack/rack/master/Rack/Builder)
(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 < Minitest::Test
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://www.sinatrarb.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](https://github.com/sinatra/sinatra/issues)
- [Twitter](https://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-2.0.8.1/README.ru.md 0000664 0000000 0000000 00000362656 13603175240 0015250 0 ustar 00root root 0000000 0000000 # Sinatra
[](https://travis-ci.org/sinatra/sinatra)
*Внимание: Этот документ является переводом английской версии и может быть
устаревшим*
Sinatra — это предметно-ориентированный каркас
([DSL](https://ru.wikipedia.org/wiki/Предметно-ориентированный_язык))
для быстрого создания функциональных веб-приложений на Ruby с минимумом усилий:
```ruby
# myapp.rb
require 'sinatra'
get '/' do
'Hello world!'
end
```
Установите gem:
```shell
gem install sinatra
```
и запустите приложение при помощи:
```shell
ruby myapp.rb
```
Оцените результат: [http://localhost:4567](http://localhost:4567)
Имейте ввиду, что изменения в коде не будут видны до тех пор, пока вы не перезапустите
сервер. Пожалуйста, перезагружайте сервер каждый раз как вносите изменения или добавьте
в проект [sinatra/reloader](http://www.sinatrarb.com/contrib/reloader).
Рекомендуется также установить Thin сервер (`gem install thin`), который автоматически
работает с Sinatra приложениями.
## Содержание
* [Sinatra](#sinatra)
* [Содержание](#Содержание)
* [Маршруты](#Маршруты)
* [Условия](#Условия)
* [Возвращаемые значения](#Возвращаемые-значения)
* [Собственные детекторы совпадений для маршрутов](#Собственные-детекторы-совпадений-для-маршрутов)
* [Статические файлы](#Статические-файлы)
* [Представления / Шаблоны](#Представления--Шаблоны)
* [Буквальные шаблоны](#Буквальные-шаблоны)
* [Доступные шаблонизаторы](#Доступные-шаблонизаторы)
* [Haml шаблоны](#haml-шаблоны)
* [Erb шаблоны](#erb-шаблоны)
* [Builder шаблоны](#builder-шаблоны)
* [Nokogiri шаблоны](#nokogiri-шаблоны)
* [Sass шаблоны](#sass-шаблоны)
* [SCSS шаблоны](#scss-шаблоны)
* [Less шаблоны](#less-шаблоны)
* [Liquid шаблоны](#liquid-шаблоны)
* [Markdown шаблоны](#markdown-шаблоны)
* [Textile шаблоны](#textile-шаблоны)
* [RDoc шаблоны](#rdoc-шаблоны)
* [AsciiDoc шаблоны](#asciidoc-шаблоны)
* [Radius шаблоны](#radius-шаблоны)
* [Markaby шаблоны](#markaby-шаблоны)
* [RABL шаблоны](#rabl-шаблоны)
* [Slim шаблоны](#slim-шаблоны)
* [Creole шаблоны](#creole-шаблоны)
* [MediaWiki шаблоны](#mediawiki-шаблоны)
* [CoffeeScript шаблоны](#coffeescript-шаблоны)
* [Stylus шаблоны](#stylus-шаблоны)
* [Yajl шаблоны](#yajl-шаблоны)
* [WLang шаблоны](#wlang-шаблоны)
* [Доступ к переменным в шаблонах](#Доступ-к-переменным-в-шаблонах)
* [Шаблоны с `yield` и вложенные лэйауты](#Шаблоны-с-yield-и-вложенные-лэйауты)
* [Включённые шаблоны](#Включённые-шаблоны)
* [Именованные шаблоны](#Именованные-шаблоны)
* [Привязка файловых расширений](#Привязка-файловых-расширений)
* [Добавление собственного движка рендеринга](#Добавление-собственного-движка-рендеринга)
* [Использование пользовательской логики для поиска шаблона](#Использование-пользовательской-логики-для-поиска-шаблона)
* [Фильтры](#Фильтры)
* [Методы-помощники](#Методы-помощники)
* [Использование сессий](#Использование-сессий)
* [Безопасность сессии](#Безопасность-сессии)
* [Конфигурация сессии](#Конфигурация-сессии)
* [Выбор вашей собственной "прослойки" сессии](#Выбор-вашей-собственной-прослойки-сессии)
* [Прерывание](#Прерывание)
* [Передача](#Передача)
* [Вызов другого маршрута](#Вызов-другого-маршрута)
* [Установка тела, статус кода и заголовков ответа](#Установка-тела-статус-кода-и-заголовков-ответа)
* [Потоковые ответы](#Потоковые-ответы)
* [Логирование](#Логирование)
* [Mime-типы](#mime-типы)
* [Генерирование URL](#Генерирование-url)
* [Перенаправление (редирект)](#Перенаправление-редирект)
* [Управление кэшированием](#Управление-кэшированием)
* [Отправка файлов](#Отправка-файлов)
* [Доступ к объекту запроса](#Доступ-к-объекту-запроса)
* [Вложения](#Вложения)
* [Работа со временем и датами](#Работа-со-временем-и-датами)
* [Поиск файлов шаблонов](#Поиск-файлов-шаблонов)
* [Конфигурация](#Конфигурация)
* [Настройка защиты от атак](#Настройка-защиты-от-атак)
* [Доступные настройки](#Доступные-настройки)
* [Режим, окружение](#Режим-окружение)
* [Обработка ошибок](#Обработка-ошибок)
* [Not Found](#not-found)
* [Error](#error)
* [Rack "прослойки"](#rack-прослойки)
* [Тестирование](#Тестирование)
* [Sinatra::Base — "прослойки", библиотеки и модульные приложения](#sinatrabase--прослойки-библиотеки-и-модульные-приложения)
* [Модульные приложения против классических](#Модульные-приложения-против-классических)
* [Запуск модульных приложений](#Запуск-модульных-приложений)
* [Запуск классических приложений с config.ru](#Запуск-классических-приложений-с-configru)
* [Когда использовать config.ru?](#Когда-использовать-configru)
* [Использование Sinatra в качестве "прослойки"](#Использование-sinatra-в-качестве-прослойки)
* [Создание приложений "на лету"](#Создание-приложений-на-лету)
* [Области видимости и привязка](#Области-видимости-и-привязка)
* [Область видимости приложения / класса](#Область-видимости-приложения--класса)
* [Область видимости запроса / экземпляра](#Область-видимости-запроса--экземпляра)
* [Область видимости делегирования](#Область-видимости-делегирования)
* [Командная строка](#Командная-строка)
* [Многопоточность](#Многопоточность)
* [Системные требования](#Системные-требования)
* [Самая свежая версия](#Самая-свежая-версия)
* [При помощи Bundler](#При-помощи-bundler)
* [Версии](#Версии)
* [Дальнейшее чтение](#Дальнейшее-чтение)
## Маршруты
В Sinatra маршрут — это пара: <HTTP метод> и <шаблон URL>. Каждый маршрут
связан с блоком кода:
```ruby
get '/' do
# .. что-то показать ..
end
post '/' do
# .. что-то создать ..
end
put '/' do
# .. что-то заменить ..
end
patch '/' do
# .. что-то изменить ..
end
delete '/' do
# .. что-то удалить ..
end
options '/' do
# .. что-то ответить ..
end
link '/' do
# .. что-то подключить ..
end
unlink '/' do
# .. что-то отключить ..
end
```
Маршруты сверяются с запросом в порядке очерёдности их записи в файле
приложения. Первый же совпавший с запросом маршрут и будет вызван.
Маршруты с конечным слэшем отличаются от маршрутов без него:
```ruby
get '/foo' do
# не соответствует "GET /foo/"
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|
# соответствует "GET /hello/foo" и "GET /hello/bar",
# где params['name'] - это 'foo' или 'bar'
# n хранит params['name']
"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 /\/hello\/([\w]+)/ do
"Hello, #{params['captures'].first}!"
end
```
Или с параметром блока:
```ruby
# Соответствует "GET /meta/hello/world", "GET /hello/world/1234" и т.д.
get %r{/hello/([\w]+)} do |c|
"Hello, #{c}!"
end
```
Шаблоны маршрутов могут иметь необязательные параметры:
```ruby
get '/posts/:format?' do
# соответствует "GET /posts/", "GET /posts/json", "GET /posts/xml" и т.д.
end
```
Маршруты также могут использовать параметры запроса:
```ruby
get '/posts' do
# соответствует "GET /posts?title=foo&author=bar"
title = params['title']
author = params['author']
# используются переменные title и author; запрос не обязателен для маршрута /posts
end
```
**Имеейте ввиду**: если вы не отключите защиту от обратного пути в директориях
(_path traversal_, см. ниже), путь запроса может быть изменён до начала
поиска подходящего маршрута.
Вы можете настроить Mustermann опции, используемые для данного маршрута, путём передачи в `:mustermann_opts` хэш:
```ruby
get '\A/posts\z', :mustermann_opts => { :type => :regexp, :check_anchors => false } do
# в точности соответствует /posts, с явной привязкой
"If you match an anchored pattern clap your hands!"
end
```
Это похоже на [условие](#Условия), но это не так! Эти опции будут объеденины в глобальный `:mustermann_opts` хэш, описанный
[ниже](#Доступные-настройки).
### Условия
Маршруты могут включать в себя различные условия совпадений, такие как, например,
строка агента пользователя (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
```
`provides` ищет заголовок запроса `Accept`.
Вы можете с лёгкостью задавать собственные условия:
```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
```
Ипользуйте 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
"Your Account Details"
end
get "/only/admin/", :auth => :admin do
"Only admins are allowed here!"
end
```
### Возвращаемые значения
Возвращаемое значение блока маршрута ограничивается телом ответа, которое
будет передано HTTP клиенту, или, по крайней мере, следующей "прослойке" (middleware)
в Rack стеке. Чаще всего это строка как в примерах выше. Но также приемлемы и
другие значения.
Вы можете вернуть любой объект, который будет либо корректным Rack ответом,
либо объектом Rack body, либо кодом состояния HTTP:
* массив с тремя переменными: `[код (Integer), заголовки (Hash), тело ответа
(должно отвечать на #each)]`;
* массив с двумя переменными: `[код (Integer), тело ответа (должно отвечать
на #each)]`;
* объект, отвечающий на `#each`, который передает только строковые типы
данных в этот блок;
* Integer, представляющий код состояния 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
```
Или с использованием негативной опережающей проверки (отрицательное look-ahead условие):
```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).
Если же в качестве значения передан символ, то его значение будет интерпретировано
как наименования файла шаблона лэйаута.
Пример: erb :index, :layout => !request.xhr?
- content_type
-
Content-Type отображенного шаблона. По умолчанию: задаётся шаблонизатором.
- scope
-
Область видимости, в которой рендерятся шаблоны. По умолчанию: экземпляр
приложения. Если вы измените эту опцию, то переменные экземпляра и
методы-помощники станут недоступными в ваших шаблонах.
- layout_engine
-
Шаблонизатор, который следует использовать для отображения лэйаута.
Полезная опция для шаблонизаторов, в которых отсутствует поддержка
лэйаутов. По умолчанию: тот же шаблонизатор, что используется и для самого
шаблона. Пример: set :rdoc, :layout_engine => :erb
- layout_options
-
Специальные опции, используемые только для рендеринга лэйаута. Пример:
set :rdoc, :layout_options => { :views => 'views/layouts' }
По умолчанию в качестве пути для хранения шаблонов принята директория `./views`.
Чтобы назначить другую директорию с шаблонами необходимо изменить настройки:
```ruby
set :views, settings.root + '/templates'
```
Важное замечание: вы всегда должны ссылаться на шаблоны при помощи символов
(Symbol), даже тогда, когда они расположены в поддиректории (в этом случае
используйте конструкции вида `:'subdir/template'`). Вы должны использовать
символы в связи с тем, что в ином случае шаблонизаторы попросту отображают
любые строки, переданные им.
### Буквальные шаблоны
```ruby
get '/' do
haml '%div.title Hello World'
end
```
Отобразит шаблон, содержимое которого передано строкой. Опционально можно
указать дополнительные опции `:path` и `:line` для того, чтобы улучшить бэктрейс.
Делайте это в том случае, если строка определена в некотором файле, к которому
можно указать путь и номер строки, где расположена исходная строка:
```ruby
get '/' do
haml '%div.title Hello World', :path => 'examples/file.haml', :line => 3
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 шаблоны
В 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`.
#### AsciiDoc шаблоны
Зависимости |
Asciidoctor |
Расширения файлов |
.asciidoc, .adoc и .ad |
Пример |
asciidoc :README, :layout_engine => :erb |
Так как в AsciiDoc шаблонах невозможно вызывать методы из Ruby напрямую, то вы
почти всегда будете передавать в шаблон локальные переменные.
#### 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`.
#### MediaWiki шаблоны
Зависимости |
WikiCloth |
Расширения файлов |
.mediawiki и .mw |
Пример |
mediawiki :wiki, :layout_engine => :erb |
В разметке MediaWiki невозможно вызывать методы или передавать локальные переменные.
Следовательно, вам, скорее всего, придётся использовать этот шаблон совместно
с другим шаблонизатором:
```ruby
erb :overview, :locals => { :text => mediawiki(:introduction) }
```
Обратите внимание на то, что вы можете вызывать метод `mediawiki` из других шаблонов:
```ruby
%h1 Hello From Haml!
%p= mediawiki(:greetings)
```
Вы не можете вызывать Ruby код из MediaWiki, соответственно вы не можете
использовать лэйауты на MediaWiki. Тем не менее, существует возможность использовать
один шаблонизатор для отображения шаблона, а другой для лэйаута при помощи
опции `:layout_engine`.
#### CoffeeScript шаблоны
#### Stylus шаблоны
Перед тем, как использовать шаблоны Stylus, необходимо сперва подключить
`stylus` и `stylus/tilt`:
```ruby
require 'sinatra'
require 'stylus'
require 'stylus/tilt'
get '/' do
stylus :example
end
```
#### 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`), то вы почти всегда будете передавать в шаблон локальные
переменные. Лэйауты также могут быть описаны при помощи WLang.
### Доступ к переменным в шаблонах
Шаблоны интерпретируются в том же контексте, что и обработчики маршрутов.
Переменные экземпляра, установленные в процессе обработки маршрутов, будут
доступны напрямую в шаблонах:
```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) обычно представляет собой шаблон, который исполняет
`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#readme).
### Использование пользовательской логики для поиска шаблона
Для того, чтобы реализовать собственный механизм поиска шаблона,
необходимо написать метод `#find_template`:
```ruby
configure do
set :views [ './views/a', './views/b' ]
end
def find_template(views, name, engine, &block)
Array(views).each do |v|
super(v, name, engine, &block)
end
end
```
## Фильтры
`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
```
### Безопасность сессии
Для того, чтобы повысить безопасность, данные сессии в файле 'cookie'
подписываются ключом сессии с использованием `HMAC-SHA1`. Этот ключ сессии
должен быть оптимальным криптографическим 'secure random' значением соответствующей
длины, которая для `HMAC-SHA1` больше или равна 64 байтам (512 бит, 128
шестнадцатеричных символов). Не рекомендуется использовать ключ, длина
которого менее 32 байт (256 бит, 64 шестнадцатеричных символа). Поэтому
**очень важно**, чтобы вы не просто составили значение ключа, а использовали
безопасный генератор случайных чисел для его создания. Люди очень плохо
придумывают случайные значения.
По умолчанию, Sinatra создаёт для вас безопасный случайный ключ сессии из
32 байт, однако он будет меняться при каждом перезапуске приложения. Если у
вас есть несколько экземпляров вашего приложения, и вы доверили Sinatra
генерацию ключа, то каждый экземпляр будет иметь отличный ключ сессии,
что, вероятно, не совсем то, что вам необходимо.
Для лучшей безопасности и удобства использования
[рекомендуется](https://12factor.net/config) генерировать случайный безопасный
ключ и хранить его в переменной среды на каждом хосте, на котором запущено
приложение, чтобы все экземпляры вашего приложения использовали один и тот
же ключ. Вы должны периодически менять значение ключа сессии на новое.
Вот несколько примеров того, как вы можете создать 64-байтный ключ
и установить его:
**Генерация ключа сессии**
```text
$ ruby -e "require 'securerandom'; puts SecureRandom.hex(64)"
99ae8af...snip...ec0f262ac
```
**Генерация ключа сессии (бонусные пункты)**
Используйте [гем 'sysrandom'](https://github.com/cryptosphere/sysrandom#readme).
Предпочтительнее использовать системные средства RNG для генерации случайных
значений вместо пространства пользователя `OpenSSL`, который в настоящее время
по умолчанию используется в MRI Ruby:
```text
$ gem install sysrandom
Создание собственных расширений. Это может занять некоторое время...
Успешно установлен sysrandom-1.x
1 gem установлен
$ ruby -e "require 'sysrandom/securerandom'; puts SecureRandom.hex(64)"
99ae8af...snip...ec0f262ac
```
**Переменная среды для ключа сессии**
Задайте переменной среды `SESSION_SECRET` значение, которое вы
сгенерировали. Данная переменная автоматически будет использована Sinatra.
Сделайте это значение постоянным при перезагрузке вашего
сервера. Поскольку метод для генерации будет различным в разных системах,
то код ниже приведён только в качестве примера:
```bash
# echo "export SESSION_SECRET=99ae8af...snip...ec0f262ac" >> ~/.bashrc
```
**Конфигурация приложения**
В целях безопасности настройте конфигурацию вашего приложения таким образом,
чтобы оно генерировало случайный безопасный ключ тогда, когда переменная
среды `SESSION_SECRET` не доступна.
В качестве бонусных пунктов здесь тоже используйте
[гем 'sysrandom'gem](https://github.com/cryptosphere/sysrandom):
```ruby
require 'securerandom'
# -или- require 'sysrandom/securerandom'
set :session_secret, ENV.fetch('SESSION_SECRET') { SecureRandom.hex(64) }
```
#### Конфигурация сессии
Если вы хотите больше настроек для сессий, вы можете задать их, передав хэш
опций в параметр `sessions`:
```ruby
set :sessions, :domain => 'foo.com'
```
Чтобы сделать сессию доступной другим приложениям, размещенным на поддоменах
foo.com, добавьте *.* перед доменом:
```ruby
set :sessions, :domain => '.foo.com'
```
#### Выбор вашей собственной "прослойки" сессии
Обратите внимание на то, что при использовании `enable :sessions` все данные
сохраняются в куках (cookies). Это может быть не совсем то, что вы хотите (например,
сохранение больших объёмов данных увеличит ваш трафик). В таком случае вы
можете использовать альтернативную Rack "прослойку" (middleware), реализующую
механизм сессий. Для этого используете один из способов ниже:
```ruby
enable :sessions
set :session_store, Rack::Session::Pool
```
Или установите параметры сессии при помощи хэша опций:
```ruby
set :sessions, :expire_after => 2592000
set :session_store, Rack::Session::Pool
```
Вы также можете **не вызывать** `enable :sessions`, а вместо этого использовать
необходимую вам Rack прослойку так же, как вы это обычно делаете.
Очень важно обратить внимание на то, что когда вы используете этот метод,
основной способ защиты сессии **не будет включён по умолчанию**.
Вам также потребуется добавить следующие Rack middleware для этого:
```ruby
use Rack::Session::Pool, :expire_after => 2592000
use Rack::Protection::RemoteToken
use Rack::Protection::SessionHijacking
```
Смотрите раздел ["Настройка защиты от атак"](#Настройка-защиты-от-атак) для более подробной информации.
### Прерывание
Чтобы незамедлительно прервать обработку запроса внутри фильтра или маршрута,
используйте следующую команду:
```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; https://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](https://w3c.github.io/eventsource/),
и может служить основой для [WebSockets](https://en.wikipedia.org/wiki/WebSocket).
Также такой подход можно использовать для увеличения производительности в том случае,
когда какая-то часть контента (а не весь) зависит от медленного ресурса.
Обратите внимание на то, что возможности стриминга, особенно количество одновременно
обслуживаемых запросов, очень сильно зависят от используемого веб-сервера.
Некоторые серверы могут и вовсе не поддерживать стриминг. Если сервер не
поддерживает стриминг, то все данные будут отправлены за один раз сразу после
того, как блок, переданный в `stream`, завершится. Стриминг вообще не работает
при использовании Shotgun.
Если метод используется с параметром `keep_open`, то он не будет вызывать
`close` у объекта потока, что позволит вам закрыть его позже в любом другом
месте. Это работает только с событийными серверами, например, с Thin и
Rainbows. Другие же серверы всё равно будут закрывать поток:
```ruby
# long polling
set :server, :thin
connections = []
get '/subscribe' do
# регистрация клиента в событиях сервера
stream(:keep_open) do |out|
connections << out
# удаление "мёртвых клиентов"
connections.reject!(&:closed?)
end
end
post '/:message' do
connections.each do |out|
# уведомить клиента о новом сообщении
out << params['message'] << "\n"
# указать клиенту на необходимость снова соединиться
out.close
end
# допуск
"message received"
end
```
Также клиент может закрыть соединение при попытке записи в сокет. В связи с
этим рекомендуется выполнить проверку `out.closed?` прежде, чем пытаться произвести запись.
### Логирование
В области видимости запроса метод `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://www.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](https://en.wikipedia.org/wiki/HTTP_ETag#Strong_and_weak_validation):
```ruby
etag @article.sha1, :weak
```
Эти методы-помощники не станут ничего кэшировать, однако они дадут
необходимую информацию для вашего кэша. Если вы ищете лёгкое решение для
кэширования, попробуйте [rack-cache](https://github.com/rtomayko/rack-cache#readme):
```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. Если вы используете
этот вспомогательный метод, 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, 2016')
"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
```
Следующий пример будет выполнен только тогда, когда окружение
(переменная `APP_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/sinatra/sinatra/tree/master/rack-protection#readme) для защиты
приложения от простых атак. Вы можете легко выключить эту защиту (что сделает
ваше приложение чрезвычайно уязвимым к большому числу различных уязвимостей):
```ruby
disable :protection
```
Чтобы отключить какой-либо конкретный уровень защиты, передайте хэш опций
в параметр `protection`:
```ruby
set :protection, :except => :path_traversal
```
Вы также можете отключить сразу несколько уровней защиты:
```ruby
set :protection, :except => [:path_traversal, :session_hijacking]
```
По умолчанию Sinatra будет устанавливать `session based` защиту только если
включена опция `:sessions`. См. ["Использование сессий""](#Использование-сессий).
Иногда вы захотите настроить сессии "вне" приложения Sinatra, например, в
config.ru или при помощи отдельного экземпляра `Rack::Builder`. В таком случае
вы также можете настроить `session based` защиту, передав опцию `:session`:
```ruby
set :protection, :session => true
```
### Доступные настройки
- absolute_redirects
-
если отключено, то Sinatra будет позволять использование относительных
перенаправлений, но при этом перестанет соответствовать RFC 2616 (HTTP
1.1), который разрешает только абсолютные перенаправления.
-
включайте эту опцию, если ваше приложение работает за обратным прокси,
который настроен не совсем корректно. Обратите внимание на тот факт, что
метод url всё равно будет генерировать абсолютные URL в том случае,
если вы не передадите false вторым аргументом.
- отключено по умолчанию.
- add_charset
-
mime-типы, к которым метод content_type будет автоматически добавлять
информацию о кодировке. Вам следует добавлять значения к этой опции
вместо её переопределения: settings.add_charset << "application/foobar"
- app_file
-
путь к главному файлу приложения, используется для нахождения корневой
директории проекта, директорий с шаблонами и статическими файлами,
вложенных шаблонов.
- bind
-
используемый IP-адрес (по умолчанию: 0.0.0.0 или
localhost если опция `environment` настроена на "development").
Используется только встроенным сервером.
- default_encoding
- кодировка, если неизвестна (по умолчанию: "utf-8").
- dump_errors
- отображать ошибки в логе.
- environment
-
текущее окружение, по умолчанию, значение ENV['APP_ENV'] или
"development", если ENV['APP_ENV'] недоступна.
- logging
- использовать логер.
- lock
-
создаёт блокировку для каждого запроса, которая гарантирует обработку
только одного запроса в текущий момент времени в Ruby процессе.
-
включайте в том случае, если ваше приложение не потокобезопасно (thread-safe).
Отключено по умолчанию.
- method_override
-
использовать "магический" параметр _method для поддержки
PUT/DELETE форм в браузерах, которые не поддерживают данные методы.
- mustermann_opts
-
хэш настроек по умолчанию для перехода к Mustermann.new при компиляции
маршрутов маршрутизации.
- port
-
порт, на котором будет работать сервер.
Используется только встроенным сервером.
- prefixed_redirects
-
добавлять или нет параметр request.script_name к редиректам, если не
задан абсолютный путь. Таким образом, redirect '/foo' будет вести себя
как redirect to('/foo'). Отключено по умолчанию.
- protection
- включена или нет защита от атак. Смотрите секцию защиты от атак выше.
- public_dir
- алиас для public_folder.
- public_folder
-
путь к общедоступной директории, откуда будут раздаваться файлы.
Используется, только если включена раздача статических файлов
(см. опцию static ниже). Берётся из настройки app_file,
если не установлена.
- quiet
-
отключает журналы, созданные командами запуска и остановки Sinatra.
false по умолчанию.
- reload_templates
-
перезагружать или нет шаблоны на каждый запрос. Включено в режиме
разработки.
- root
-
путь к корневой директории проекта. Берётся из настройки app_file,
если не установлен.
- raise_errors
-
выбрасывать исключения (будет останавливать приложение).
По умолчанию включено только в окружении "test".
- run
-
если включено, Sinatra будет самостоятельно запускать веб-сервер. Не
включайте, если используете rackup или аналогичные средства.
- running
- работает ли сейчас встроенный сервер? Не меняйте эту опцию!
- server
-
сервер или список серверов, которые следует использовать в качестве
встроенного сервера. Порядок задаёт приоритет, по умолчанию зависит
от реализации Ruby.
- server_settings
-
Если вы используете в качестве сервера WEBrick, например для работы в
режиме разработки, то вы можете передать набор опций для server_settings,
таких как SSLEnable или SSLVerifyClient. Тем не менее, такие
серверы как Puma или Thin не поддерживают эти параметры, поэтому вы можете
устанавливать server_settings указав его как метод при вызове
configure.
- sessions
-
включить сессии на основе кук (cookie) на базе Rack::Session::Cookie.
Смотрите раздел "Использование сессий" выше.
- session_store
-
используемая Rack "прослойка" для сессии. По умолчанию 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 для
обработки запросов.
- traps
- указывает, должна ли Sinatra обрабатывать системные сигналы.
- views
-
путь к директории с шаблонами. Берётся из настройки app_file в том
случае, если не установлен.
- x_cascade
-
Указывает, необходимо ли устанавливать заголовок X-Cascade если никакие маршруты не совпадают.
true по умолчанию.
## Режим, окружение
Есть 3 предопределённых режима работы приложения (окружения): `"development"`,
`"production"` и `"test"`. Режим может быть задан через переменную окружения `APP_ENV`.
Значение по умолчанию — `"development"`. В этом режиме работы все шаблоны
перезагружаются между запросами, а также задаются специальные обработчики
`not_found` и `error`, чтобы вы могли увидеть стек вызовов. В окружениях
`"production"` и `"test"` шаблоны по умолчанию кэшируются.
Для запуска приложения в определённом окружении установите переменную
окружения `APP_ENV`:
```shell
APP_ENV=production ruby my_app.rb
```
Вы можете использовать предопределённые методы `development?`, `test?` и
`production?`, чтобы определить текущее окружение.
```ruby
get '/' do
if settings.development?
"development!"
else
"not development!"
end
end
```
## Обработка ошибок
Обработчики ошибок исполняются в том же контексте, что и маршруты и
`before`-фильтры, а это означает, что всякие прелести вроде `haml`, `erb`,
`halt` и т.д. доступны и им.
### Not Found
В случае возникновения исключения `Sinatra::NotFound` или возврата кода ответа 404
будет вызван обработчик `not_found`:
```ruby
not_found do
'This is nowhere to be found.'
end
```
### Error
Обработчик ошибок `error` будет вызван тогда, когда исключение выброшено из блока
маршрута либо из фильтра. Тем не менее, обратите внимание на то, что в режиме разработки
он будет запускаться только в том случае, если вы установите опцию "show exceptions"
на `: after_handler`:
```ruby
set :show_exceptions, :after_handler
```
Объект-исключение доступен как переменная `sinatra.error` в Rack:
```ruby
error do
'Sorry there was a nasty error - ' + env['sinatra.error'].message
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](https://rack.github.io/) - минимальный стандартный
интерфейс для веб-фреймворков на Ruby. Одной из самых интересных для
разработчиков возможностей Rack является поддержка "прослоек" ("middleware") —
компонентов, находящихся "между" сервером и вашим приложением, которые
отслеживают и/или манипулируют 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://www.rubydoc.info/github/rack/rack/master/Rack/Builder) 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),
или в [Rack wiki](https://github.com/rack/rack/wiki/List-of-Middleware).
## Тестирование
Тесты для Sinatra приложений могут быть написаны при помощи любых библиотек или
фреймворков, поддерживающих тестирование Rack. Разработчики гема Sinatra рекомендуют
использовать [Rack::Test](http://www.rubydoc.info/github/brynary/rack-test/master/frames):
```ruby
require 'my_sinatra_app'
require 'minitest/autorun'
require 'rack/test'
class MyAppTest < Minitest::Test
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_user_agent
get '/', {}, 'HTTP_USER_AGENT' => 'Songbird'
assert_equal "You're using Songbird!", last_response.body
end
end
```
Примечание: если вы используете Sinatra в модульном стиле, замените
`Sinatra::Application` в примере выше на имя класса вашего приложения.
## Sinatra::Base — "прослойки", библиотеки и модульные приложения
Описание вашего приложения на верхнем уровне хорошо работает для микроприложений,
но имеет значительные недостатки при создании многоразовых компонентов, таких как
Rack "прослойка", Rails metal, простые библиотеки с серверным компонентом или
даже расширения Sinatra. Верхний уровень предполагает конфигурацию стиля
микроприложений (например, одиночный файл приложения, `./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`, иначе все DSL методы,
предоставляемые Sinatra, будут импортированы в глобальное пространство
имён.
* Поместите все маршруты, обработчики ошибок, фильтры и опции в подкласс
`Sinatra::Base`.
`Sinatra::Base` — это чистый лист. Большинство опций, включая встроенный
сервер, по умолчанию отключены. Смотрите
[Опции и конфигурация](http://www.sinatrarb.com/configuration.html)
для детальной информации об опциях и их поведении. Если вы хотите, чтобы
поведение было более похоже на то, когда вы определяете своё приложение
на верхнем уровне (также известно как классический стиль), вы можете
унаследоваться от `Sinatra::Application`:
```ruby
require 'sinatra/base'
class MyApp < Sinatra::Application
get '/' do
'Hello world!'
end
end
```
### Модульные приложения против классических
Вопреки всеобщему убеждению, в классическом стиле (самом простом) нет ничего
плохого. Если этот стиль подходит вашему приложению, вы не обязаны
переписывать его в модульное приложение.
Основным недостатком классического стиля является тот факт, что у вас может
быть только одно приложение Sinatra на один процесс Ruby. Если вы планируете
использовать больше, переключайтесь на модульный стиль. Вы можете смело
смешивать модульный и классический стили.
Переходя с одного стиля на другой, примите во внимание следующие изменения в
настройках:
Опция |
Классический |
Модульный |
Модульный |
app_file |
файл с приложением |
файл с подклассом Sinatra::Base |
файл с подклассом Sinatra::Application |
run |
$0 == app_file |
false |
false |
logging |
true |
false |
true |
method_override |
true |
false |
true |
inline_templates |
true |
false |
true |
static |
true |
File.exist?(public_folder) |
true |
### Запуск модульных приложений
Есть два общепринятых способа запускать модульные приложения: запуск напрямую
при помощи `run!`:
```ruby
# my_app.rb
require 'sinatra/base'
class MyApp < Sinatra::Base
# ... здесь код приложения ...
# запускаем сервер, если исполняется текущий файл
run! if app_file == $0
end
```
Затем:
```shell
ruby my_app.rb
```
Или при помощи конфигурационного файла `config.ru`, который позволяет
использовать любой Rack-совместимый сервер приложений:
```ruby
# config.ru (запускается при помощи Rackup)
require './my_app'
run MyApp
```
Запускаем:
```shell
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/Hamami/Roda/...):
```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 (запускается при помощи 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
```
Это особенно полезно для тестирования расширений 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, patch, link и unlink блоках;
* 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 приложения могут быть запущены напрямую:
```shell
ruby myapp.rb [-h] [-x] [-q] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER]
```
Поддерживаемые опции:
```
-h # раздел помощи
-p # указание порта (по умолчанию 4567)
-o # указание хоста (по умолчанию 0.0.0.0)
-e # указание окружения, режима (по умолчанию development)
-s # указание rack сервера/обработчика (по умолчанию thin)
-q # включить тихий режим для сервера (по умолчанию выключен)
-x # включить мьютекс-блокировку (по умолчанию выключена)
```
### Многопоточность
_Данный раздел является перефразированным [ответом пользователя Konstantin](https://stackoverflow.com/a/6282999/5245129) на StackOverflow_
Sinatra не навязывает каких-либо моделей параллелизма, но для этих целей можно
использовать любой Rack обработчик (сервер), например Thin, Puma или WEBrick. Сама
по себе Sinatra потокобезопасна, поэтому нет никаких проблем в использовании
поточной модели параллелизма в Rack обработчике. Это означает, что когда
запускается сервер, вы должны указать правильный метод вызова для конкретного
Rack обработчика. Пример ниже показывает, как можно запустить мультипоточный
Thin сервер:
```ruby
# app.rb
require 'sinatra/base'
class App < Sinatra::Base
get '/' do
"Hello, World"
end
end
App.run!
```
Для запуска сервере выполните следующую команду:
```shell
thin --threaded start
```
## Системные требования
Следующие версии Ruby официально поддерживаются:
- Ruby 2.2
-
Версия 2.2 полностью поддерживается и рекомендуется. В настоящее время нет
планов отказаться от официальной поддержки.
- Rubinius
-
Rubinius официально поддерживается (Rubinius >= 2.x). Рекомендуется
выполнить gem install puma.
- JRuby
-
Официально поддерживается последний стабильный релиз JRuby. Не
рекомендуется использовать расширений на C в JRuby. Рекомендуется
выполнить gem install trinidad.
Версии Ruby ниже 2.2.2 более не поддерживаются в Sinatra 2.0.
Мы также следим за предстоящими к выходу версиями Ruby.
Следующие реализации Ruby не поддерживаются официально, однако известно, что
на них запускается Sinatra:
* старые версии JRuby и Rubinius;
* Ruby Enterprise Edition;
* MacRuby, Maglev, IronRuby;
* Ruby 1.9.0 и 1.9.1 (настоятельно не рекомендуются к использованию).
То, что версия официально не поддерживается, означает, что, если что-то не
работает на этой версии, а на поддерживаемой работает — это не наша проблема,
а их.
Мы также запускаем наши CI-тесты на ruby-head (будущие версии MRI), но мы не
можем ничего гарантировать, так как ведётся постоянная разработка.
Предполагается, что предстоящие релизы 2.x будут полностью поддерживаться.
Sinatra должна работать на любой операционной системе, в которой есть одна из
указанных выше версий Ruby.
Если вы запускаете MacRuby, вы должны выполнить `gem install control_tower`.
Пока невозможно запустить Sinatra на Cardinal, SmallRuby, BlueRuby и на любой
версии Ruby ниже 2.2.
## Самая свежая версия
Если вы хотите использовать самый последний релиз Sinatra, не бойтесь запускать
своё приложение вместе с кодом из master ветки Sinatra, так как она весьма стабильна.
Мы также время от времени выпускаем предварительные версии, которые вы можете
установить следующим образом:
```shell
gem install sinatra --pre
```
таким образом вы сможете воспользоваться некоторыми самыми последними возможностями.
### При помощи Bundler
Если вы хотите запускать своё приложение с последней версией Sinatra, то
рекомендуем использовать [Bundler](http://bundler.io).
Для начала установите Bundler, если у вас его ещё нет:
```shell
gem install bundler
```
Затем создайте файл `Gemfile` в директории вашего проекта:
```ruby
source 'https://rubygems.org'
gem 'sinatra', :github => 'sinatra/sinatra'
# другие зависимости
gem 'haml' # в том случае, если используете haml
```
Имейте ввиду, что вам необходимо будет указывать все зависимости вашего приложения
в `Gemfile`. Необходимые зависимости самой библиотеки Sinatra (Rack и Tilt)
будут автоматически скачаны и добавлены Bundler.
Теперь вы можете запускать своё приложение следующим образом:
```shell
bundle exec ruby myapp.rb
```
## Версии
Sinatra использует [Semantic Versioning](https://semver.org/): как SemVer, так и
SemVerTag.
## Дальнейшее чтение
* [Веб-сайт проекта](http://www.sinatrarb.com/) — Дополнительная
документация, новости и ссылки на другие ресурсы.
* [Участие в проекте](http://www.sinatrarb.com/contributing) — Обнаружили
баг? Нужна помощь? Написали патч?
* [Отслеживание проблем/ошибок](https://github.com/sinatra/sinatra/issues)
* [Twitter](https://twitter.com/sinatra)
* [Группы рассылки](https://groups.google.com/forum/#!forum/sinatrarb)
* IRC: [#sinatra](irc://chat.freenode.net/#sinatra) на [Freenode](https://freenode.net)
* [Sinatra и Друзья](https://sinatrarb.slack.com) на Slack, а так же
[ссылка для инвайта](https://sinatra-slack.herokuapp.com/).
* [Sinatra Book](https://github.com/sinatra/sinatra-book) учебник и сборник рецептов
* [Sinatra Recipes](http://recipes.sinatrarb.com/) сборник рецептов
* API документация к [последнему релизу](http://www.rubydoc.info/gems/sinatra)
или [текущему HEAD](http://www.rubydoc.info/github/sinatra/sinatra) на
http://www.rubydoc.info/
* [Сервер непрерывной интеграции](https://travis-ci.org/sinatra/sinatra)
sinatra-2.0.8.1/README.zh.md 0000664 0000000 0000000 00000222673 13603175240 0015235 0 ustar 00root root 0000000 0000000 # Sinatra
*注:本文档是英文版的翻译,内容更新有可能不及时。如有不一致的地方,请以英文版为准。*
Sinatra 是一门基于
Ruby 的[领域专属语言](https://en.wikipedia.org/wiki/Domain-specific_language),致力于轻松、快速地创建网络应用:
```ruby
# myapp.rb
require 'sinatra'
get '/' do
'Hello world!'
end
```
安装 Sinatra 这个 gem:
```shell
gem install sinatra
```
然后运行 myapp.rb 中的代码:
```shell
ruby myapp.rb
```
在该地址查看: [http://localhost:4567](http://localhost:4567)
推荐运行 `gem install thin` 安装 Thin。这样,Sinatra 会优先选择 Thin 作为服务器。
## 目录
* [Sinatra](#sinatra)
* [目录](#目录)
* [路由](#路由)
* [条件](#条件)
* [返回值](#返回值)
* [自定义路由匹配器](#自定义路由匹配器)
* [静态文件](#静态文件)
* [视图 / 模板](#视图--模板)
* [字面量模板](#字面量模板)
* [可选的模板语言](#可选的模板语言)
* [Haml 模板](#haml-模板)
* [Erb 模板](#erb-模板)
* [Builder 模板](#builder-模板)
* [Nokogiri 模板](#nokogiri-模板)
* [Sass 模板](#sass-模板)
* [SCSS 模板](#scss-模板)
* [Less 模板](#less-模板)
* [Liquid 模板](#liquid-模板)
* [Markdown 模板](#markdown-模板)
* [Textile 模板](#textile-模板)
* [RDoc 模板](#rdoc-模板)
* [AsciiDoc 模板](#asciidoc-模板)
* [Radius 模板](#radius-模板)
* [Markaby 模板](#markaby-模板)
* [RABL 模板](#rabl-模板)
* [Slim 模板](#slim-模板)
* [Creole 模板](#creole-模板)
* [MediaWiki 模板](#mediawiki-模板)
* [CoffeeScript 模板](#coffeescript-模板)
* [Stylus 模板](#stylus-模板)
* [Yajl 模板](#yajl-模板)
* [WLang 模板](#wlang-模板)
* [在模板中访问变量](#在模板中访问变量)
* [带 `yield` 的模板和嵌套布局](#带-yield-的模板和嵌套布局)
* [内联模板](#内联模板)
* [具名模板](#具名模板)
* [关联文件扩展名](#关联文件扩展名)
* [添加自定义模板引擎](#添加自定义模板引擎)
* [自定义模板查找逻辑](#自定义模板查找逻辑)
* [过滤器](#过滤器)
* [辅助方法](#辅助方法)
* [使用会话](#使用会话)
* [中断请求](#中断请求)
* [传递请求](#传递请求)
* [触发另一个路由](#触发另一个路由)
* [设置响应主体、状态码和响应首部](#设置响应主体状态码和响应首部)
* [响应的流式传输](#响应的流式传输)
* [日志](#日志)
* [媒体类型](#媒体类型)
* [生成 URL](#生成-url)
* [浏览器重定向](#浏览器重定向)
* [缓存控制](#缓存控制)
* [发送文件](#发送文件)
* [访问请求对象](#访问请求对象)
* [附件](#附件)
* [处理日期和时间](#处理日期和时间)
* [查找模板文件](#查找模板文件)
* [配置](#配置)
* [配置攻击防护](#配置攻击防护)
* [可选的设置](#可选的设置)
* [环境](#环境)
* [错误处理](#错误处理)
* [未找到](#未找到)
* [错误](#错误)
* [Rack 中间件](#rack-中间件)
* [测试](#测试)
* [Sinatra::Base - 中间件、库和模块化应用](#sinatrabase---中间件库和模块化应用)
* [模块化风格 vs. 经典风格](#模块化风格-vs-经典风格)
* [运行一个模块化应用](#运行一个模块化应用)
* [使用 config.ru 运行经典风格的应用](#使用-configru-运行经典风格的应用)
* [何时使用 config.ru?](#何时使用-configru)
* [把 Sinatra 当作中间件使用](#把-sinatra-当作中间件使用)
* [创建动态应用](#创建动态应用)
* [作用域和绑定](#作用域和绑定)
* [应用/类作用域](#应用类作用域)
* [请求/实例作用域](#请求实例作用域)
* [代理作用域](#代理作用域)
* [命令行](#命令行)
* [多线程](#多线程)
* [必要条件](#必要条件)
* [紧跟前沿](#紧跟前沿)
* [通过 Bundler 使用 Sinatra](#通过-bundler-使用-sinatra)
* [使用自己本地的 Sinatra](#使用自己本地的-sinatra)
* [全局安装](#全局安装)
* [版本](#版本)
* [更多资料](#更多资料)
## 路由
在 Sinatra 中,一个路由分为两部分:HTTP 方法和 URL 匹配范式。每个路由都有一个要执行的代码块:
```ruby
get '/' do
.. 显示内容 ..
end
post '/' do
.. 创建内容 ..
end
put '/' do
.. 替换内容 ..
end
patch '/' do
.. 修改内容 ..
end
delete '/' do
.. 删除内容 ..
end
options '/' do
.. 显示命令列表 ..
end
link '/' do
.. 建立某种联系 ..
end
unlink '/' do
.. 解除某种联系 ..
end
```
路由按照它们定义时的顺序进行匹配。第一个与请求匹配的路由会被调用。
路由范式可以包括具名参数,具名参数可以通过 `params` hash 访问:
```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|
# 匹配 "GET /hello/foo" 和 "GET /hello/bar"
# params['name'] 的值是 'foo' 或者 'bar'
# n 存储 params['name'] 的值
"Hello #{n}!"
end
```
路由范式也可以包含通配符参数, 参数值可以通过 `params['splat']` 数组访问。
```ruby
get '/say/*/to/*' do
# 匹配 "GET /say/hello/to/world"
params['splat'] # => ["hello", "world"]
end
get '/download/*.*' do
# 匹配 "GET /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 /\/hello\/([\w]+)/ do
"Hello, #{params['captures'].first}!"
end
```
或者使用代码块参数:
```ruby
get %r{/hello/([\w]+)} do |c|
# 匹配 "GET /meta/hello/world"、"GET /hello/world/1234" 等
"Hello, #{c}!"
end
```
路由范式可以包含可选参数:
```ruby
get '/posts/:format?' do
# 匹配 "GET /posts/" 和任意扩展 "GET /posts/json"、"GET /posts/xml" 等
end
```
路由也可以使用查询参数:
```ruby
get '/posts' do
# 匹配 "GET /posts?title=foo&author=bar"
title = params['title']
author = params['author']
# 使用 title 和 author 变量;对于 /posts 路由来说,查询字符串是可选的
end
```
顺便一提,除非你禁用了路径遍历攻击防护(见下文),请求路径可能在匹配路由前发生改变。
你也可以通过`:mustermann_opt`选项定义[Mustermann](https://github.com/sinatra/mustermann)来匹配路由。
```ruby
get '\A/posts\z', :mustermann_opts => { :type => :regexp, :check_anchors => false } do
# matches /posts exactly, with explicit anchoring
"If you match an anchored pattern clap your hands!"
end
```
它看起来像一个[条件](https://github.com/sinatra/sinatra/blob/master/README.zh.md#%E6%9D%A1%E4%BB%B6),但实际不是!这些选项将被合并到全局的`mustermann_opts`。
### 条件
路由可以包含各种匹配条件,比如 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
```
`provides` 会搜索请求的 Accept 首部字段。
也可以轻易地使用自定义条件:
```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
```
对于一个需要提供多个值的条件,可以使用 splat:
```ruby
set(:auth) do |*roles| # <- 注意此处使用了 splat
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 客户端的响应主体,或者至少决定了在
Rack 堆栈中的下一个中间件。大多数情况下,返回值是一个字符串,就像上面的例子中的一样。但是,其它类型的值也是可以接受的。
你可以返回任何对象,该对象要么是一个合理的 Rack 响应,要么是一个 Rack body 对象,要么是 HTTP 状态码:
* 一个包含三个元素的数组: `[状态 (Integer), 响应首部 (Hash), 响应主体 (可以响应 #each 方法)]`
* 一个包含两个元素的数组: `[状态 (Integer), 响应主体 (可以响应 #each 方法)]`
* 一个响应 `#each` 方法,只传回字符串的对象
* 一个代表状态码的数字
例如,我们可以轻松地实现流式传输:
```ruby
class Stream
def each
100.times { |i| yield "#{i}\n" }
end
end
get('/') { Stream.new }
```
也可以使用 `stream` 辅助方法(见下文描述)以减少样板代码并在路由中直接使用流式传输。
### 自定义路由匹配器
如上文所示,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` 目录提供服务。可以通过设置`:public_folder` 选项设定一个不同的位置:
```ruby
set :public_folder, File.dirname(__FILE__) + '/static'
```
请注意 public 目录名并没有包含在 URL 中。文件 `./public/css/style.css` 可以通过
`http://example.com/css/style.css` 访问。
可以使用 `:static_cache_control` 设置(见下文)添加 `Cache-Control` 首部信息。
## 视图 / 模板
每一门模板语言都将自身的渲染方法暴露给
Sinatra 调用。这些渲染方法只是简单地返回字符串。
```ruby
get '/' do
erb :index
end
```
这段代码会渲染 `views/index.erb` 文件。
除了模板文件名,也可以直接传入模板内容:
```ruby
get '/' do
code = "<%= Time.now %>"
erb code
end
```
渲染方法接受第二个参数,即选项 hash:
```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
-
传递给模板文档的 locals 对象列表。对于 partials
很方便。例如: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
- layout_options
-
渲染布局的特殊选项。例如:
set :rdoc, :layout_options => { :views => 'views/layouts' }
Sinatra 假定模板文件直接位于 `./views` 目录。要使用不同的视图目录:
```ruby
set :views, settings.root + '/templates'
```
需要牢记的一点是,你必须通过符号引用模板, 即使它们存放在子目录下
(在这种情况下,使用 `:'subdir/template'` 或 `'subdir/template'.to_sym`)。
如果你不使用符号,渲染方法会直接渲染你传入的任何字符串。
### 字面量模板
```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" } |
`builder` 渲染方法也接受一个代码块,用于内联模板(见例子)。
#### Nokogiri 模板
依赖项 |
nokogiri |
文件扩展名 |
.nokogiri |
例子 |
nokogiri { |xml| xml.em "hi" } |
`nokogiri` 渲染方法也接受一个代码块,用于内联模板(见例子)。
#### 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`),你几乎总是需要传递 locals 对象给它。
#### Markdown 模板
不能在 markdown 中调用 Ruby 方法,也不能传递 locals 给它。
因此,你一般会结合其它的渲染引擎来使用它:
```ruby
erb :overview, :locals => { :text => markdown(:introduction) }
```
请注意你也可以在其它模板中调用 markdown 方法:
```ruby
%h1 Hello From Haml!
%p= markdown(:greetings)
```
因为不能在 Markdown 中使用 Ruby 语言,你不能使用 Markdown 书写的布局。
不过,使用其它渲染引擎作为模板的布局是可能的,这需要通过传入 `:layout_engine` 选项。
#### Textile 模板
依赖项 |
RedCloth |
文件扩展名 |
.textile |
例子 |
textile :index, :layout_engine => :erb |
不能在 textile 中调用 Ruby 方法,也不能传递 locals 给它。
因此,你一般会结合其它的渲染引擎来使用它:
```ruby
erb :overview, :locals => { :text => textile(:introduction) }
```
请注意你也可以在其他模板中调用 `textile` 方法:
```ruby
%h1 Hello From Haml!
%p= textile(:greetings)
```
因为不能在 Textile 中调用 Ruby 方法,你不能用 Textile 书写布局。
不过,使用其它渲染引擎作为模版的布局是可能的,这需要通过传递 `:layout_engine` 选项。
#### RDoc 模板
依赖项 |
RDoc |
文件扩展名 |
.rdoc |
例子 |
rdoc :README, :layout_engine => :erb |
不能在 rdoc 中调用 Ruby 方法,也不能传递 locals 给它。
因此,你一般会结合其它的渲染引擎来使用它:
```ruby
erb :overview, :locals => { :text => rdoc(:introduction) }
```
请注意你也可以在其他模板中调用 `rdoc` 方法:
```ruby
%h1 Hello From Haml!
%p= rdoc(:greetings)
```
因为不能在 RDoc 中调用 Ruby 方法,你不能用 RDoc 书写布局。
不过,使用其它渲染引擎作为模版的布局是可能的,这需要通过传递 `:layout_engine` 选项。
#### AsciiDoc 模板
依赖项 |
Asciidoctor |
文件扩展名 |
.asciidoc, .adoc and .ad |
例子 |
asciidoc :README, :layout_engine => :erb |
因为不能在 AsciiDoc 模板中直接调用 Ruby 方法,你几乎总是需要传递 locals 对象给它。
#### Radius 模板
依赖项 |
Radius |
文件扩展名 |
.radius |
例子 |
radius :index, :locals => { :key => 'value' } |
因为不能在 Radius 模板中直接调用 Ruby 方法,你几乎总是可以传递 locals 对象给它。
#### Markaby 模板
依赖项 |
Markaby |
文件扩展名 |
.mab |
例子 |
markaby { h1 "Welcome!" } |
`markaby` 渲染方法也接受一个代码块,用于内联模板(见例子)。
#### RABL 模板
依赖项 |
Rabl |
文件扩展名 |
.rabl |
例子 |
rabl :index |
#### Slim 模板
#### Creole 模板
依赖项 |
Creole |
文件扩展名 |
.creole |
例子 |
creole :wiki, :layout_engine => :erb |
不能在 creole 中调用 Ruby 方法,也不能传递 locals 对象给它。
因此你一般会结合其它的渲染引擎来使用它:
```ruby
erb :overview, :locals => { :text => creole(:introduction) }
```
注意你也可以在其它模板内调用 `creole` 方法:
```ruby
%h1 Hello From Haml!
%p= creole(:greetings)
```
因为不能在 Creole 模板文件内调用 Ruby 方法,你不能用 Creole 书写布局文件。
然而,使用其它渲染引擎作为模版的布局是可能的,这需要通过传递 `:layout_engine` 选项。
#### MediaWiki 模板
依赖项 |
WikiCloth |
文件扩展名 |
.mediawiki and .mw |
例子 |
mediawiki :wiki, :layout_engine => :erb |
在 MediaWiki 标记文件内不能调用 Ruby 方法,也不能传递 locals 对象给它。
因此你一般会结合其它的渲染引擎来使用它:
```ruby
erb :overview, :locals => { :text => mediawiki(:introduction) }
```
注意你也可以在其它模板内调用 `mediawiki` 方法:
```ruby
%h1 Hello From Haml!
%p= mediawiki(:greetings)
```
因为不能在 MediaWiki 文件内调用 Ruby 方法,你不能用 MediaWiki 书写布局文件。
然而,使用其它渲染引擎作为模版的布局是可能的,这需要通过传递 `:layout_engine` 选项。
#### CoffeeScript 模板
#### Stylus 模板
在使用 Stylus 模板之前,你需要先加载 `stylus` 和 `stylus/tilt`:
```ruby
require 'sinatra'
require 'stylus'
require 'stylus/tilt'
get '/' do
stylus :example
end
```
#### 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` 选项装饰被渲染的对象:
```javascript
var resource = {"foo":"bar","baz":"qux"};
present(resource);
```
#### WLang 模板
依赖项 |
WLang |
文件扩展名 |
.wlang |
例子 |
wlang :index, :locals => { :key => 'value' } |
因为在 WLang 中调用 Ruby 方法不符合语言习惯,你几乎总是需要传递 locals 给 WLang 木板。
然而,可以用 WLang 编写布局文件,也可以在 WLang 中使用 `yield` 方法。
### 在模板中访问变量
模板的求值发生在路由处理器内部的上下文中。模板可以直接访问路由处理器中设置的实例变量。
```ruby
get '/:id' do
@foo = Foo.find(params['id'])
haml '%h1= @foo.name'
end
```
或者,也可以显式地指定一个由局部变量组成的 locals 哈希:
```ruby
get '/:id' do
foo = Foo.find(params['id'])
haml '%h1= foo.name', :locals => { :foo => foo }
end
```
locals 哈希典型的使用情景是在别的模板中渲染 partials。
### 带 `yield` 的模板和嵌套布局
布局通常就是使用了 `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` 文件。
查看 https://github.com/rtomayko/tilt 以了解更多关于 Tilt 的信息。
### 自定义模板查找逻辑
要实现自定义的模板查找机制,你可以构建自己的 `#find_template` 方法:
```ruby
configure do
set :views, [ './views/a', './views/b' ]
end
def find_template(views, name, engine, &block)
Array(views).each do |v|
super(v, name, engine, &block)
end
end
```
## 过滤器
`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` 过滤器是不可访问的, 因为它在之后才会生成。
过滤器可以可选地带有范式, 只有请求路径满足该范式时才会执行:
```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
```
以上代码块与在应用类中包含模块等效。
### 使用会话
会话用于在请求之间保持状态。如果激活了会话,每一个用户会话都对应一个会话 hash:
```ruby
enable :sessions
get '/' do
"value = " << session['value'].inspect
end
get '/:value' do
session['value'] = params['value']
end
```
#### 会话加密
为提高安全性,cookie 中的会话数据使用`HMAC-SHA1`进行加密。会话加密的最佳实践应当是像`HMAC-SHA1`这样生成大于或等于64字节 (512 bits, 128 hex characters)的随机值。应当避免使用少于32字节(256 bits, 64 hex characters)的随机值。应当使用生成器来创建安全的密钥,而不是拍脑袋决定。
默认情况下,Sinatra会生成一个32字节的密钥,但随着应用程序的每次重新启动,它都会发生改变。如果有多个应用程序的实例,使用Sinatra生成密钥,每个实例将有不同的密钥,这可能不是您想要的。
为了更好的安全性和可用性,[建议](https://12factor.net/config)生成安全的随机密钥,并将其存储在运行应用程序的每个主机上的环境变量中,以便所有应用程序实例都将共享相同的密钥。并且应该定期更新会话密钥。下面是一些创建64比特密钥的例子:
#### 生成密钥
```ruby
$ ruby -e "require 'securerandom'; puts SecureRandom.hex(64)"
99ae8af...snip...ec0f262ac
```
#### 生成密钥(小贴士)
MRI Ruby目前认为[sysrandom gem](https://github.com/cryptosphere/sysrandom)使用系统的随机数生成器要比用户态的`OpenSSL`好。
```ruby
$ gem install sysrandom
Building native extensions. This could take a while...
Successfully installed sysrandom-1.x
1 gem installed
$ ruby -e "require 'sysrandom/securerandom'; puts SecureRandom.hex(64)"
99ae8af...snip...ec0f262ac
```
#### 从环境变量使用密钥
将Sinatra的SESSION_SECRET环境变量设置为生成的值。在主机的重新启动之间保存这个值。由于这样做的方法会因系统而异,仅供说明之用:
```
# echo "export SESSION_SECRET=99ae8af...snip...ec0f262ac" >> ~/.bashrc
```
#### 应用的密钥配置
如果SESSION SECRET环境变量不可用,将把应用的随机密钥设置为不安全的。
关于[sysrandom gem](https://github.com/cryptosphere/sysrandom)的更多用法:
```ruby
require 'securerandom'
# -or- require 'sysrandom/securerandom'
set :session_secret, ENV.fetch('SESSION_SECRET') { SecureRandom.hex(64) }
```
#### 会话配置
如果你想进一步配置会话,可以在设置 `sessions` 时提供一个选项 hash 作为第二个参数:
```ruby
set :sessions, :domain => 'foo.com'
```
为了在 foo.com 的子域名间共享会话数据,可以在域名前添加一个 *.*:
```ruby
set :sessions, :domain => '.foo.com'
```
#### 选择你自己的会话中间件
请注意 `enable :sessions` 实际将所有的数据保存在一个 cookie 中。
这可能并不总是你想要的(cookie 中存储大量的数据会增加你的流量)。
你可以使用任何 Rack session 中间件:要达到此目的,**不要**使用 `enable :sessions`,
而是按照自己的需要引入想使用的中间件:
```ruby
enable :sessions
set :session_store, Rack::Session::Pool
```
另一种选择是不要调用enable:sessions,而是像你想要的其他中间件一样加入你的中间件。
重要的是要注意,使用此方法时,默认情况下不会启用基于会话的保护。
还需要添加Rack中间件:
```ruby
use Rack::Session::Pool, :expire_after => 2592000
use Rack::Protection::RemoteToken
use Rack::Protection::SessionHijacking
```
更多[安全防护配置](https://github.com/sinatra/sinatra#configuring-attack-protection)的信息。
### 中断请求
要想在过滤器或路由中立即中断一个请求:
```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 erb(:error)
```
### 传递请求
一个路由可以放弃对请求的处理并将处理让给下一个匹配的路由,这要通过 `pass` 实现:
```ruby
get '/guess/:who' do
pass unless params['who'] == 'Frank'
'You got me!'
end
get '/guess/*' do
'You missed!'
end
```
执行 `pass` 后,控制流从该路由代码块直接退出,并继续前进到下一个匹配的路由。
如果没有匹配的路由,将返回 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 规范。
### 设置响应主体、状态码和响应首部
推荐在路由代码块的返回值中设定状态码和响应主体。
但是,在某些场景下你可能想在别处设置响应主体,这时你可以使用 `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
```
`stream` 辅助方法允许你实现流式 API 和
[服务器端发送事件](https://w3c.github.io/eventsource/),
同时它也是实现 [WebSockets](https://en.wikipedia.org/wiki/WebSocket) 的基础。
如果你应用的部分(不是全部)内容依赖于访问缓慢的资源,它也可以用来提高并发能力。
请注意流式传输,尤其是并发请求数,高度依赖于应用所使用的服务器。
一些服务器可能根本不支持流式传输。
如果服务器不支持,传递给 `stream` 方法的代码块执行完毕之后,响应主体会一次性地发送给客户端。
Shotgun 完全不支持流式传输。
如果 `:keep_open` 作为可选参数传递给 `stream` 方法,将不会在流对象上调用 `close` 方法,
这允许你在控制流的下游某处手动关闭。该参数只对事件驱动的服务器(如 Thin 和 Rainbows)生效。
其它服务器仍会关闭流式传输:
```ruby
# 长轮询
set :server, :thin
connections = []
get '/subscribe' do
# 在服务器端的事件中注册客户端
stream(:keep_open) do |out|
connections << out
# 清除关闭的连接
connections.reject!(&:closed?)
end
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
```
该 `logger` 方法会自动参考 Rack 处理器的日志设置。
若日志被禁用,该方法会返回一个无关痛痒的对象,所以你完全不必担心这会影响路由和过滤器。
注意只有 `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']` 的值作为日志工具,无论该值是什么。
### 媒体类型
使用 `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://www.google.com/', 'wrong place, buddy'
```
用 `redirect back` 可以把用户重定向到原始页面:
```ruby
get '/foo' do
"do something"
end
get '/bar' do
do_something
redirect back
end
```
如果想传递参数给 redirect,可以用查询字符串:
```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](https://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
```
使用 `:statis_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
- Content-Type 响应首部的值,如果未指定,会根据文件扩展名猜测。
- disposition
-
Content-Disposition 响应首部的值,
可选的值有: nil (默认)、:attachment 和
:inline
- length
- Content-Length 响应首部的值,默认是文件的大小。
- status
-
将要返回的状态码。当以一个静态文件作为错误页面时,这很有用。
如果 Rack 处理器支持的话,Ruby 进程也能使用除 streaming 以外的方法。
如果你使用这个辅助方法, Sinatra会自动处理 range 请求。
### 访问请求对象
传入的请求对象可以在请求层(过滤器、路由、错误处理器内部)通过 `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_param"] # some_param 参数的值。[] 是访问 params hash 的捷径
request.referrer # 客户端的 referrer 或者 '/'
request.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 hash
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
```
`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`。
同样的,模板的路径(和内容)会在 development 以外的模式下被缓存。
你应该时刻提醒自己这一点, 如果你真的想写一个非常疯狂的方法的话。
## 配置
在启动时运行一次,在任何环境下都是如此:
```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
```
只有当环境 (`APP_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/sinatra/sinatra/tree/master/rack-protection#readme)
来抵御常见的攻击。你可以轻易地禁用该行为(但这会大大增加应用被攻击的概率)。
```ruby
disable :protection
```
为了绕过某单层防护,可以设置 `protection` 为一个选项 hash:
```ruby
set :protection, :except => :path_traversal
```
你可以传入一个数组,以禁用一系列防护措施:
```ruby
set :protection, :except => [:path_traversal, :session_hijacking]
```
默认地,如果 `:sessions` 是启用的,Sinatra 只会使用基于会话的防护措施。
当然,有时你可能想根据自己的需要设置会话。
在这种情况下,你可以通过传入 `:session` 选项来开启基于会话的防护。
```ruby
use Rack::Session::Pool
set :protection, :session => true
```
### 可选的设置
- absolute_redirects
-
如果被禁用,Sinatra 会允许使用相对路径重定向。
然而这样的话,Sinatra 就不再遵守 RFC 2616 (HTTP 1.1), 该协议只允许绝对路径重定向。
-
如果你的应用运行在一个未恰当设置的反向代理之后,你需要启用这个选项。
注意 url 辅助方法仍然会生成绝对 URL,除非你传入false 作为第二参数。
- 默认禁用。
- add_charset
-
设置 content_type 辅助方法会自动为媒体类型加上字符集信息。
你应该添加而不是覆盖这个选项:
settings.add_charset << "application/foobar"
- app_file
-
主应用文件的路径,用来检测项目的根路径, views 和 public 文件夹和内联模板。
- bind
-
绑定的 IP 地址 (默认: 0.0.0.0,开发环境下为 localhost)。
仅对于内置的服务器有用。
- default_encoding
- 默认编码 (默认为 "utf-8")。
- dump_errors
- 在日志中显示错误。
- environment
-
当前环境,默认是 ENV['APP_ENV'],
或者 "development" (如果 ENV['APP_ENV'] 不可用)。
- logging
- 使用 logger。
- lock
- 对每一个请求放置一个锁,只使用进程并发处理请求。
- 如果你的应用不是线程安全则需启动。默认禁用。
- method_override
-
使用 _method 魔法,以允许在不支持的浏览器中在使用 put/delete 方法提交表单。
- port
- 监听的端口号。只对内置服务器有用。
- prefixed_redirects
-
如果没有使用绝对路径,是否添加 request.script_name 到重定向请求。
如果添加,redirect '/foo' 会和 redirect to('/foo') 相同。
默认禁用。
- protection
- 是否启用网络攻击防护。参见上面的保护部分
- public_dir
- public_folder 的别名。见下文。
- public_folder
-
public 文件存放的路径。只有启用了静态文件服务(见下文的 static)才会使用。
如果未设置,默认从 app_file 推断。
- reload_templates
-
是否每个请求都重新载入模板。在开发模式下开启。
- root
- 到项目根目录的路径。默认从 app_file 设置推断。
- raise_errors
-
抛出异常(会停止应用)。
当 environment 设置为 "test" 时会默认开启,其它环境下默认禁用。
- run
- 如果启用,Sinatra 会负责 web 服务器的启动。若使用 rackup 或其他方式则不要启用。
- running
- 内置的服务器在运行吗? 不要修改这个设置!
- server
- 服务器,或用于内置服务器的服务器列表。顺序表明了优先级,默认顺序依赖 Ruby 实现。
- sessions
-
使用 Rack::Session::Cookie,启用基于 cookie 的会话。
查看“使用会话”部分以获得更多信息。
- show_exceptions
-
当有异常发生时,在浏览器中显示一个 stack trace。
当 environment 设置为 "development" 时,默认启用,
否则默认禁用。
-
也可以设置为 :after_handler,
这会在浏览器中显示 stack trace 之前触发应用级别的错误处理。
- static
- 决定 Sinatra 是否服务静态文件。
- 当服务器能够自行服务静态文件时,会禁用。
- 禁用会增强性能。
- 在经典风格中默认启用,在模块化应用中默认禁用。
- static_cache_control
-
当 Sinatra 提供静态文件服务时,设置此选项为响应添加 Cache-Control 首部。
使用 cache_control 辅助方法。默认禁用。
-
当设置多个值时使用数组:
set :static_cache_control, [:public, :max_age => 300]
- threaded
-
若设置为 true,会告诉 Thin 使用 EventMachine.defer 处理请求。
- traps
- Sinatra 是否应该处理系统信号。
- views
- views 文件夹的路径。若未设置则会根据 app_file 推断。
- x_cascade
- 若没有路由匹配,是否设置 X-Cascade 首部。默认为 true。
## 环境
Sinatra 中有三种预先定义的环境:"development"、"production" 和 "test"。
环境可以通过 `APP_ENV` 环境变量设置。默认值为 "development"。
在开发环境下,每次请求都会重新加载所有模板,
特殊的 `not_found` 和 `error` 错误处理器会在浏览器中显示 stack trace。
在测试和生产环境下,模板默认会缓存。
在不同的环境下运行,设置 `APP_ENV` 环境变量:
```shell
APP_ENV=production ruby my_app.rb
```
可以使用预定义的三种方法: `development?`、`test?` 和 `production?` 来检查当前环境:
```ruby
get '/' do
if settings.development?
"development!"
else
"not development"
end
end
```
## 错误处理
错误处理器在与路由和 before 过滤器相同的上下文中运行,
这意味着你可以使用许多好东西,比如 `haml`, `erb`, `halt`,等等。
### 未找到
当一个 `Sinatra::NotFound` 错误被抛出时,或者当响应的状态码是 404 时,
会调用 `not_found` 处理器:
```ruby
not_found do
'This is nowhere to be found.'
end
```
### 错误
在任何路由代码块或过滤器抛出异常时,会调用 `error` 处理器。
但注意在开发环境下只有将 show exceptions 项设置为 `:after_handler` 时,才会生效。
```ruby
set :show_exceptions, :after_handler
```
可以用 Rack 变量 `sinatra.error` 访问异常对象:
```ruby
error do
'Sorry there was a nasty error - ' + env['sinatra.error'].message
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
```
在开发环境下,Sinatra会使用特殊的 `not_found` 和 `error` 处理器,
以便在浏览器中显示美观的 stack traces 和额外的调试信息。
## Rack 中间件
Sinatra 依赖 [Rack](http://rack.github.io/), 一个面向 Ruby 网络框架的最小化标准接口。
Rack 最有趣的功能之一是支持“中间件”——位于服务器和你的应用之间的组件,
它们监控或操作 HTTP 请求/响应以提供多种常用功能。
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://www.rubydoc.info/github/rack/rack/master/Rack/Builder)
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#readm) 或
[Rack wiki](https://github.com/rack/rack/wiki/List-of-Middleware)
中找到有用的中间件。
## 测试
可以使用任何基于 Rack 的测试程序库或者框架来编写Sinatra的测试。
推荐使用 [Rack::Test](http://www.rubydoc.info/github/brynary/rack-test/master/frames):
```ruby
require 'my_sinatra_app'
require 'minitest/autorun'
require 'rack/test'
class MyAppTest < Minitest::Test
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 的模块化风格,应该用你应用的类名替代 `Sinatra::Application`。
## Sinatra::Base - 中间件、库和模块化应用
在顶层定义你的应用很适合微型项目,
但是在构建可复用的组件(如 Rack 中间件、Rails metal、带服务器组件的库或 Sinatra 扩展)时,
却有相当大的缺陷。
顶层 DSL 认为你采用的是微型应用风格的配置 (例如:唯一应用文件、
`./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://www.sinatrarb.com/configuration.html)
以查看可用选项的具体细节和它们的行为。如果你想让你的应用更像顶层定义的应用(即经典风格),
你可以继承 `Sinatra::Applicaiton`。
```ruby
require 'sinatra/base'
class MyApp < Sinatra::Application
get '/' do
'Hello world!'
end
end
```
### 模块化风格 vs. 经典风格
与通常的认识相反,经典风格并没有任何错误。
如果它适合你的应用,你不需要切换到模块化风格。
与模块化风格相比,经典风格的主要缺点在于,每个 Ruby 进程只能有一个 Sinatra 应用。
如果你计划使用多个 Sinatra 应用,应该切换到模块化风格。
你也完全可以混用模块化风格和经典风格。
如果从一种风格转换到另一种,你需要注意默认设置中的一些细微差别:
设置 |
经典风格 |
模块化风格 |
模块化风格 |
app_file |
加载 sinatra 的文件 |
继承 Sinatra::Base 的文件 |
继承 Sinatra::Application 的文件 |
run |
$0 == app_file |
false |
false |
logging |
true |
false |
true |
method_override |
true |
false |
true |
inline_templates |
true |
false |
true |
static |
true |
File.exist?(public_folder) |
true |
### 运行一个模块化应用
模块化应用的启动有两种常见方式,其中之一是使用 `run!` 方法主动启动:
```ruby
# my_app.rb
require 'sinatra/base'
class MyApp < Sinatra::Base
# ... 这里是应用代码 ...
# 如果直接执行该文件,那么启动服务器
run! if app_file == $0
end
```
执行该文件就会启动服务器:
```shell
ruby my_app.rb
```
另一种方式是使用 `config.ru` 文件,这种方式允许你使用任何 Rack 处理器:
```ruby
# config.ru (用 rackup 启动)
require './my_app'
run MyApp
```
运行:
```shell
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' && params['password'] == 'admin'
session['user_name'] = params['name']
else
redirect '/login'
end
end
end
class MyApp < Sinatra::Base
# 中间件的执行发生在 before 过滤器之前
use LoginScreen
before do
unless session['user_name']
halt "Access denied, please login."
end
end
get('/') { "Hello #{session['user_name']}." }
end
```
### 创建动态应用
有时你希望在运行时创建新应用,而不必把应用预先赋值给常量。这时可以使用 `Sinatra.new`:
```ruby
require 'sinatra/base'
my_app = Sinatra.new { get('/') { "hi" } }
my_app.run!
```
`Sinatra.new` 接受一个可选的参数,表示要继承的应用:
```ruby
# config.ru (用 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
```
当你测试 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` 值的 procs/blocks 内部
* 传递给 `Sinatra.new` 的代码块内部
你可以这样访问变量域对象(应用类):
* 通过传递给 configure 代码块的对象 (`configure { |c| ... }`)
* 在请求作用域中使用 `settings`
### 请求/实例作用域
对于每个请求,Sinatra 会创建应用类的一个新实例。所有的处理器代码块都在该实例对象的作用域中运行。
在该作用域中, 你可以访问 `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、patch、link 和 unlink 代码块内部
* before 和 after 过滤器内部
* 辅助方法内部
* 模板/视图内部
### 代理作用域
代理作用域只是把方法转送到类作用域。
然而,它与类作用域的行为并不完全相同, 因为你并不能在代理作用域获得类的绑定。
只有显式地标记为供代理使用的方法才是可用的,
而且你不能和类作用域共享变量/状态。(解释:你有了一个不同的 `self`)。
你可以通过调用 `Sinatra::Delegator.delegate :method_name` 显式地添加方法代理。
以下位置绑定的是代理变量域:
* 顶层绑定,如果你执行了 `require "sinatra"`
* 扩展了 `Sinatra::Delegator` 这一 mixin 的对象内部
自己在这里看一下源码:[Sinatra::Delegator
mixin](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/base.rb#L1609-1633)
已经
[被扩展进了 main 对象](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/main.rb#L28-30)。
## 命令行
可以直接运行 Sinatra 应用:
```shell
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 # 打开互斥锁 (默认是 off)
```
### 多线程
_根据 Konstantin 的 [这个 StackOverflow 答案] [so-answer] 改写_
Sinatra 本身并不使用任何并发模型,而是将并发的任务留给底层的
Rack 处理器(服务器),如 Thin、Puma 或 WEBrick。Sinatra 本身是线程安全的,所以
Rack 处理器使用多线程并发模型并无任何问题。这意味着在启动服务器时,你必须指定特定
Rack 处理器的正确调用方法。
下面的例子展示了如何启动一个多线程的 Thin 服务器:
```ruby
# app.rb
require 'sinatra/base'
class App < Sinatra::Base
get '/' do
"Hello, World"
end
end
App.run!
```
启动服务器的命令是:
```shell
thin --threaded start
```
[so-answer]: http://stackoverflow.com/questions/6278817/is-sinatra-multi-threaded/6282999#6282999)
## 必要条件
以下 Ruby 版本受官方支持:
- Ruby 1.8.7
-
Sinatra 完全支持 1.8.7,但是,除非必要,我们推荐你升级或者切换到
JRuby 或 Rubinius。Sinatra 2.0 之前都不会取消对 1.8.7
的支持。Ruby 1.8.6 目前已不受支持。
- Ruby 1.9.2
-
Sinatra 完全支持 1.9.2。
不要使用 1.9.2p0,它在运行 Sinatra 程序时会产生 segmentation faults 错误。
至少在 Sinatra 1.5 发布之前,官方对 1.9.2 的支持仍会继续。
- Ruby 1.9.3
-
Sinatra 完全支持并推荐使用 1.9.3。请注意从更早的版本迁移到 1.9.3 会使所有的会话失效。
直到 Sinatra 2.0 发布之前,官方仍然会支持 1.9.3。
- Ruby 2.x
-
Sinatra 完全支持并推荐使用 2.x。目前尚无停止支持 2.x 的计划。
- Rubinius
-
Sinatra 官方支持 Rubinius (Rubinius >= 2.x)。推荐 gem install puma。
- JRuby
-
Sinatra 官方支持 JRuby 的最新稳定版本,但不推荐在 JRuby 上使用 C 扩展。
推荐 gem install trinidad。
我们也在时刻关注新的 Ruby 版本。
以下 Ruby 实现不受 Sinatra 官方支持,但可以运行 Sinatra:
* 老版本 JRuby 和 Rubinius
* Ruby 企业版
* MacRuby、Maglev、IronRuby
* Ruby 1.9.0 和 1.9.1 (不推荐使用)
不受官方支持的意思是,如果仅在不受支持的 Ruby 实现上发生错误,我们认为不是我们的问题,而是该实现的问题。
我们同时也针对 ruby-head (MRI 的未来版本)运行 CI,但由于 ruby-head 一直处在变化之中,
我们不能作任何保证。我们期望完全支持未来的 2.x 版本。
Sinatra 应该会运行在任何支持上述 Ruby 实现的操作系统上。
如果你使用 MacRuby,你应该 `gem install control_tower`。
Sinatra 目前不支持 Cardinal、SmallRuby、BlueRuby 或其它 1.8.7 之前的 Ruby 版本。
## 紧跟前沿
如果你想使用 Sinatra 的最新代码,请放心使用 master 分支来运行你的程序,它是相当稳定的。
我们也会不定期推出 prerelease gems,所以你也可以运行
```shell
gem install sinatra --pre
```
来获得最新的特性。
### 通过 Bundler 使用 Sinatra
如果你想在应用中使用最新的 Sinatra,推荐使用 [Bundler](http://bundler.io)。
首先,安装 Bundler,如果你还没有安装的话:
```shell
gem install bundler
```
然后,在你的项目目录下创建一个 `Gemfile`:
```ruby
source 'https://rubygems.org'
gem 'sinatra', :github => "sinatra/sinatra"
# 其它依赖
gem 'haml' # 假如你使用 haml
gem 'activerecord', '~> 3.0' # 也许你还需要 ActiveRecord 3.x
```
请注意你必须在 `Gemfile` 中列出应用的所有依赖项。
然而, Sinatra 的直接依赖项 (Rack 和 Tilt) 则会被 Bundler 自动获取和添加。
现在你可以这样运行你的应用:
```shell
bundle exec ruby myapp.rb
```
### 使用自己本地的 Sinatra
创建一个本地克隆,并通过 `$LOAD_PATH` 里的 `sinatra/lib` 目录运行你的应用:
```shell
cd myapp
git clone git://github.com/sinatra/sinatra.git
ruby -I sinatra/lib myapp.rb
```
为了在未来更新 Sinatra 源代码:
```shell
cd myapp/sinatra
git pull
```
### 全局安装
你可以自行编译 Sinatra gem:
```shell
git clone git://github.com/sinatra/sinatra.git
cd sinatra
rake sinatra.gemspec
rake install
```
如果你以 root 身份安装 gems,最后一步应该是:
```shell
sudo rake install
```
## 版本
Sinatra 遵循[语义化版本](http://semver.org),无论是 SemVer 还是 SemVerTag。
## 更多资料
* [项目官网](http://www.sinatrarb.com/) - 更多文档、新闻和其它资源的链接。
* [贡献](http://www.sinatrarb.com/contributing) - 找到一个 bug?需要帮助?有了一个 patch?
* [问题追踪](https://github.com/sinatra/sinatra/issues)
* [Twitter](https://twitter.com/sinatra)
* [邮件列表](http://groups.google.com/group/sinatrarb/topics)
* IRC: [#sinatra](irc://chat.freenode.net/#sinatra) on http://freenode.net
* [Sinatra & Friends](https://sinatrarb.slack.com) on Slack,点击
[这里](https://sinatra-slack.herokuapp.com/) 获得邀请。
* [Sinatra Book](https://github.com/sinatra/sinatra-book/) Cookbook 教程
* [Sinatra Recipes](http://recipes.sinatrarb.com/) 社区贡献的实用技巧
* http://www.rubydoc.info/ 上[最新版本](http://www.rubydoc.info//gems/sinatra)或[当前 HEAD](http://www.rubydoc.info/github/sinatra/sinatra) 的 API 文档
* [CI 服务器](https://travis-ci.org/sinatra/sinatra)
sinatra-2.0.8.1/RELEASING.md 0000664 0000000 0000000 00000003634 13603175240 0015163 0 ustar 00root root 0000000 0000000 # Releasing Sinatra 🥂
This document explains releasing process for all Sinatra gems.
Since everything is bundled in same repo (except `Mustermann`), we now
have one rake task to cut a release.
(Please refer to [Mustermann](https://github.com/sinatra/mustermann) if that also needs a release.)
### Releasing
For relesing new version of [`sinatra`, `sinatra-contrib`, `rack-protection`], this is the procedure:
1. Update `VERSION` file with target version
2. Run `bundle exec rake release:all`
3. ???
4. Profit!!!
Thats it!
This rake task can be broken down as:
* Pick up latest version string from `VERSION` file
* Run all tests to ensure gems are not broken
* Update `version.rb` file in all gems with latest `VERSION`
* Create a new commit with new `VERSION` and `version.rb` files
* Tag the commit with same version
* Push the commit and tags to github
* Package all the gems, ie create all `.gem` files
* Ensure that all the gems can be installed locally
* If no issues, push all gems to upstream.
In addition to above rake task, there are other rake tasks which can help
with development.
### Packaging
These rake tasks will generate `.gem` and `.tar.gz` files. For each gem,
there is one dedicated rake task.
```sh
# Build sinatra-contrib package
$ bundle exec rake package:sinatra-contrib
# Build rack-protection package
$ bundle exec rake package:rack-protection
# Build sinatra package
$ bundle exec rake package:sinatra
# Build all packages
$ bundle exec rake package:all
```
### Packaging and installing locally
These rake tasks will package all the gems, and install them locally
```sh
# Build and install sinatra-contrib gem locally
$ bundle exec rake install:sinatra-contrib
# Build and install rack-protection gem locally
$ bundle exec rake install:rack-protection
# Build and install sinatra gem locally
$ bundle exec rake install:sinatra
# Build and install all gems locally
$ bundle exec rake install:all
```
sinatra-2.0.8.1/Rakefile 0000664 0000000 0000000 00000015551 13603175240 0014776 0 ustar 00root root 0000000 0000000 require 'rake/clean'
require 'rake/testtask'
require 'fileutils'
require 'date'
task :default => :test
task :spec => :test
CLEAN.include "**/*.rbc"
def source_version
@source_version ||= File.read(File.expand_path("../VERSION", __FILE__)).strip
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 = ['-r rubygems'] if defined? Gem
t.ruby_opts << '-I.'
t.warning = true
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 = ["-r rubygems"] if defined? Gem
t.ruby_opts << "-I."
t.warning = true
end
# Rcov ================================================================
namespace :test do
desc 'Measures test coverage'
task :coverage do
rm_f "coverage"
ENV['COVERAGE'] = '1'
Rake::Task['test'].invoke
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", "Zachary Scott"]
desc "list of contributors"
task :thanks, ['release:all', :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(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)
GEMS_AND_ROOT_DIRECTORIES = {
"sinatra" => ".",
"sinatra-contrib" => "./sinatra-contrib",
"rack-protection" => "./rack-protection"
}
def package(gem, ext='')
"pkg/#{gem}-#{source_version}" + ext
end
directory 'pkg/'
CLOBBER.include('pkg')
GEMS_AND_ROOT_DIRECTORIES.each do |gem, directory|
file package(gem, '.gem') => ["pkg/", "#{directory + '/' + gem}.gemspec"] do |f|
sh "cd #{directory} && gem build #{gem}.gemspec"
mv directory + "/" + File.basename(f.name), f.name
end
file package(gem, '.tar.gz') => ["pkg/"] do |f|
sh <<-SH
git archive \
--prefix=#{gem}-#{source_version}/ \
--format=tar \
HEAD -- #{directory} | gzip > #{f.name}
SH
end
end
namespace :package do
GEMS_AND_ROOT_DIRECTORIES.each do |gem, directory|
desc "Build #{gem} packages"
task gem => %w[.gem .tar.gz].map { |e| package(gem, e) }
end
desc "Build all packages"
task :all => GEMS_AND_ROOT_DIRECTORIES.keys
end
namespace :install do
GEMS_AND_ROOT_DIRECTORIES.each do |gem, directory|
desc "Build and install #{gem} as local gem"
task gem => package(gem, '.gem') do
sh "gem install #{package(gem, '.gem')}"
end
end
desc "Build and install all of the gems as local gems"
task :all => GEMS_AND_ROOT_DIRECTORIES.keys
end
namespace :release do
GEMS_AND_ROOT_DIRECTORIES.each do |gem, directory|
desc "Release #{gem} as a package"
task gem => "package:#{gem}" do
sh <<-SH
gem install #{package(gem, '.gem')} --local &&
gem push #{package(gem, '.gem')}
SH
end
end
desc "Commits the version to github repository"
task :commit_version do
%w[
lib/sinatra
sinatra-contrib/lib/sinatra/contrib
rack-protection/lib/rack/protection
].each do |path|
path = File.join(path, 'version.rb')
File.write(path, File.read(path).sub(/VERSION = '(.+?)'/, "VERSION = '#{source_version}'"))
end
sh <<-SH
git commit --allow-empty -a -m '#{source_version} release' &&
git tag -s v#{source_version} -m '#{source_version} release' &&
git push && (git push origin || true) &&
git push --tags && (git push origin --tags || true)
SH
end
desc "Release all gems as packages"
task :all => [:test, :commit_version] + GEMS_AND_ROOT_DIRECTORIES.keys
end
end
sinatra-2.0.8.1/SECURITY.md 0000664 0000000 0000000 00000005504 13603175240 0015117 0 ustar 00root root 0000000 0000000 # Reporting a security bug
All security bugs in Sinatra should be reported to the core team through our private mailing list [sinatra-security@googlegroups.com](https://groups.google.com/group/sinatra-security). Your report will be acknowledged within 24 hours, and you’ll receive a more detailed response to your email within 48 hours indicating the next steps in handling your report.
After the initial reply to your report the security team will endeavor to keep you informed of the progress being made towards a fix and full announcement. These updates will be sent at least every five days, in reality this is more likely to be every 24-48 hours.
If you have not received a reply to your email within 48 hours, or have not heard from the security team for the past five days there are a few steps you can take:
* Contact the current security coordinator [Zachary Scott](mailto:zzak@ruby-lang.org) directly
## Disclosure Policy
Sinatra has a 5 step disclosure policy, that is upheld to the best of our ability.
1. Security report received and is assigned a primary handler. This person will coordinate the fix and release process.
2. Problem is confirmed and, a list of all affected versions is determined. Code is audited to find any potential similar problems.
3. Fixes are prepared for all releases which are still supported. These fixes are not committed to the public repository but rather held locally pending the announcement.
4. A suggested embargo date for this vulnerability is chosen and distros@openwall is notified. This notification will include patches for all versions still under support and a contact address for packagers who need advice back-porting patches to older versions.
5. On the embargo date, the [mailing list][mailing-list] and [security list][security-list] are sent a copy of the announcement. The changes are pushed to the public repository and new gems released to rubygems.
Typically the embargo date will be set 72 hours from the time vendor-sec is first notified, however this may vary depending on the severity of the bug or difficulty in applying a fix.
This process can take some time, especially when coordination is required with maintainers of other projects. Every effort will be made to handle the bug in as timely a manner as possible, however it’s important that we follow the release process above to ensure that the disclosure is handled in a consistent manner.
## Security Updates
Security updates will be posted on the [mailing list][mailing-list] and [security list][security-list].
## Comments on this Policy
If you have any suggestions to improve this policy, please send an email the core team at [sinatrarb@googlegroups.com](https://groups.google.com/group/sinatrarb).
[mailing-list]: http://groups.google.com/group/sinatrarb/topics
[security-list]: http://groups.google.com/group/sinatra-security/topics
sinatra-2.0.8.1/VERSION 0000664 0000000 0000000 00000000010 13603175240 0014361 0 ustar 00root root 0000000 0000000 2.0.8.1
sinatra-2.0.8.1/examples/ 0000775 0000000 0000000 00000000000 13603175240 0015140 5 ustar 00root root 0000000 0000000 sinatra-2.0.8.1/examples/chat.rb 0000775 0000000 0000000 00000002457 13603175240 0016417 0 ustar 00root root 0000000 0000000 #!/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-2.0.8.1/examples/simple.rb 0000775 0000000 0000000 00000000133 13603175240 0016756 0 ustar 00root root 0000000 0000000 #!/usr/bin/env ruby -I ../lib -I lib
require 'sinatra'
get('/') { 'this is a simple app' }
sinatra-2.0.8.1/examples/stream.ru 0000664 0000000 0000000 00000001064 13603175240 0017004 0 ustar 00root root 0000000 0000000 # 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-2.0.8.1/lib/ 0000775 0000000 0000000 00000000000 13603175240 0014070 5 ustar 00root root 0000000 0000000 sinatra-2.0.8.1/lib/sinatra.rb 0000664 0000000 0000000 00000000061 13603175240 0016053 0 ustar 00root root 0000000 0000000 require 'sinatra/main'
enable :inline_templates
sinatra-2.0.8.1/lib/sinatra/ 0000775 0000000 0000000 00000000000 13603175240 0015531 5 ustar 00root root 0000000 0000000 sinatra-2.0.8.1/lib/sinatra/base.rb 0000664 0000000 0000000 00000175614 13603175240 0017006 0 ustar 00root root 0000000 0000000 # coding: utf-8
# frozen_string_literal: true
# external dependencies
require 'rack'
require 'tilt'
require 'rack/protection'
require 'mustermann'
require 'mustermann/sinatra'
require 'mustermann/regular'
# stdlib dependencies
require 'thread'
require 'time'
require 'uri'
# other files we need
require 'sinatra/indifferent_hash'
require 'sinatra/show_exceptions'
require 'sinatra/version'
module Sinatra
# The request object. See Rack::Request for more info:
# http://rubydoc.info/github/rack/rack/master/Rack/Request
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
if @env.include? 'HTTP_ACCEPT' and @env['HTTP_ACCEPT'].to_s != ''
@env['HTTP_ACCEPT'].to_s.scan(HEADER_VALUE_WITH_PARAMS).
map! { |e| AcceptEntry.new(e) }.sort
else
[AcceptEntry.new('*/*')]
end
end
end
def accept?(type)
preferred_type(type).to_s.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
def params
super
rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e
raise BadRequest, "Invalid query parameters: #{Rack::Utils.escape_html(e.message)}"
end
private
class AcceptEntry
attr_accessor :params
attr_reader :entry
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://rubydoc.info/github/rack/rack/master/Rack/Response
# http://rubydoc.info/github/rack/rack/master/Rack/Response/Helpers
class Response < Rack::Response
DROP_BODY_RESPONSES = [204, 304]
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 + p.bytesize }.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?
DROP_BODY_RESPONSES.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 BadRequest < TypeError #:nodoc:
def http_status; 400 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 = Rack::Utils.status_code(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
# Rack 2.0 returns a Rack::File::Iterator here instead of
# Rack::File as it was in the previous API.
unless request.head? || value.is_a?(Rack::File::Iterator) || value.is_a?(Stream)
headers.delete 'Content-Length'
end
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\+\.\-]*:/i
uri = [host = String.new]
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.dup
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(File.dirname(settings.app_file))
result = file.serving(request, path)
result[1].each { |k,v| headers[k] ||= v }
headers['Content-Length'] = result[1]['Content-Length']
opts[:status] &&= Integer(opts[:status])
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, :s_maxage).
#
# 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 ['max-age', 's-maxage'].include? key
values << "#{key}=#{value}"
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=500
# => 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
ETAG_KINDS = [:strong, :weak]
# 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 ETAG_KINDS.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
# whether or not the status is set to 400
def bad_request?
status == 400
end
# Generates a Time object from the given value.
# Used by #expires and #last_modified.
def time_for(value)
if value.is_a? Numeric
Time.at value
elsif value.respond_to? :to_s
Time.parse value.to_s
else
value.to_time
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
@preferred_extension = nil
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 = {})
options[:exclude_outvar] = true
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 asciidoc(template, options = {}, locals = {})
render :asciidoc, 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 mediawiki(template, options = {}, locals = {})
render :mediawiki, 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.default_mapping.extensions_for(engine).each do |ext|
yield ::File.join(views, "#{name}.#{ext}") unless ext == @preferred_extension
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.merge!(engine_options) { |key, v1, v2| v1 }
# 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 && engine_options[:layout] != false)
layout = @default_layout if layout.nil? or layout == true
layout_options = options.delete(:layout_options) || {}
content_type = options.delete(:default_content_type)
content_type = options.delete(:content_type) || content_type
layout_engine = options.delete(:layout_engine) || engine
scope = options.delete(:scope) || self
exclude_outvar = options.delete(:exclude_outvar)
options.delete(:layout)
# set some defaults
options[:outvar] ||= '@_out_buf' unless exclude_outvar
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).
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.exist?(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
caller = settings.caller_locations.first
path = options[:path] || caller[0]
line = options[:line] || caller[1]
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_INSTANCE = URI::Parser.new
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
@params = IndifferentHash.new
@request = Request.new(env)
@response = Response.new
template_cache.clear if settings.reload_templates
@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, conditions, block|
returned_pass_block = process_route(pattern, conditions) do |*args|
env['sinatra.route'] = "#{@request.request_method} #{pattern}"
route_eval { block[*args] }
end
# don't wipe out pass_block in superclass
pass_block = returned_pass_block if returned_pass_block
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, conditions, block = nil, values = [])
route = @request.path_info
route = '/' if route.empty? and not settings.empty_path_info?
route = route[0..-2] if !settings.strict_paths? && route != '/' && route.end_with?('/')
return unless params = pattern.params(route)
params.delete("ignore") # TODO: better params handling, maybe turn it into "smart" object or detect changes
force_encoding(params)
@params = @params.merge(params) if params.any?
regexp_exists = pattern.is_a?(Mustermann::Regular) || (pattern.respond_to?(:patterns) && pattern.patterns.any? {|subpattern| subpattern.is_a?(Mustermann::Regular)} )
if regexp_exists
captures = pattern.match(route).captures.map { |c| URI_INSTANCE.unescape(c) if c }
values += captures
@params[:captures] = force_encoding(captures) unless captures.nil? || captures.empty?
else
values += params.values.flatten
end
catch(:pass) do
conditions.each { |c| throw :pass if c.bind(self).call == false }
block ? block[self, values] : yield(self, values)
end
rescue
@env['sinatra.error.params'] = @params
raise
ensure
params ||= {}
params.each { |k, _| @params.delete(k) } unless @env['sinatra.error.params']
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, "#{request.request_method} #{request.path_info}"
end
end
# Attempt to serve static files from public directory. Throws :halt when
# a matching file is found, returns nil otherwise.
def static!(options = {})
return if (public_dir = settings.public_folder).nil?
path = File.expand_path("#{public_dir}#{URI_INSTANCE.unescape(request.path_info)}" )
return unless File.file?(path)
env['sinatra.static_file'] = path
cache_control(*settings.static_cache_control) if settings.static_cache_control?
send_file path, options.merge(:disposition => nil)
end
# Run the block with 'throw :halt' support and apply result to the response.
def invoke
res = catch(:halt) { yield }
res = [res] if Integer === res or String === res
if Array === res and Integer === 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!
# Avoid passing frozen string in force_encoding
@params.merge!(@request.params).each do |key, val|
next unless val.respond_to?(:force_encoding)
val = val.dup if val.frozen?
@params[key] = force_encoding(val)
end
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)
if error_params = @env['sinatra.error.params']
@params = @params.merge(error_params)
end
@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
boom_message = boom.message if boom.message && boom.message != boom.class.name
if server_error?
dump_errors! boom if settings.dump_errors?
raise boom if settings.show_exceptions? and settings.show_exceptions != :after_handler
elsif not_found?
headers['X-Cascade'] = 'pass' if settings.x_cascade?
body boom_message || 'Not Found
'
elsif bad_request?
body boom_message || 'Bad Request
'
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 = ["#{Time.now.strftime("%Y-%m-%d %H:%M:%S")} - #{boom.class} - #{boom.message}:", *boom.backtrace].join("\n\t")
@env['rack.errors'].puts(msg)
end
class << self
CALLERS_TO_IGNORE = [ # :nodoc:
/\/sinatra(\/(base|main|show_exceptions))?\.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|inline))?\.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, Integer, 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)
define_singleton(option, 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.flat_map(&method(:Array))
codes << Exception if codes.empty?
codes << Sinatra::NotFound if codes.include?(404)
codes.each { |c| (@errors[c] ||= []) << args }
end
# Sugar for `error(404) { ... }`
def not_found(&block)
error(404, &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(String.new, 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 = /.*/, **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 = /.*/, **options, &block)
add_filter(:after, path, **options, &block)
end
# add a filter
def add_filter(type, path = /.*/, **options, &block)
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_folder or :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)
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
# Stop the self-hosted server if running.
def quit!
return unless running?
# Use Thin's hard #stop! if available, otherwise just #stop.
running_server.respond_to?(:stop!) ? running_server.stop! : running_server.stop
$stderr.puts "== Sinatra has ended his set (crowd applauds)" unless suppress_messages?
set :running_server, nil
set :handler_name, nil
end
alias_method :stop!, :quit!
# 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 = {}, &block)
return if running?
set options
handler = detect_rack_handler
handler_name = handler.name.gsub(/.*::/, '')
server_settings = settings.respond_to?(:server_settings) ? settings.server_settings : {}
server_settings.merge!(:Port => port, :Host => bind)
begin
start_server(handler, server_settings, handler_name, &block)
rescue Errno::EADDRINUSE
$stderr.puts "== Someone is already performing on port #{port}!"
raise
ensure
quit!
end
end
alias_method :start!, :run!
# Check whether the self-hosted server is running or not.
def running?
running_server?
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
# Starts the server by running the Rack Handler.
def start_server(handler, server_settings, handler_name)
# Ensure we initialize middleware before startup, to match standard Rack
# behavior, by ensuring an instance exists:
prototype
# Run the instance we created:
handler.run(self, server_settings) do |server|
unless suppress_messages?
$stderr.puts "== Sinatra (v#{Sinatra::VERSION}) has taken the stage on #{port} for #{environment} with backup from #{handler_name}"
end
setup_traps
set :running_server, server
set :handler_name, handler_name
server.threaded = settings.threaded if server.respond_to? :threaded=
yield server if block_given?
end
end
def suppress_messages?
handler_name =~ /cgi/i || quiet
end
def setup_traps
if traps?
at_exit { quit! }
[:INT, :TERM].each do |signal|
old_handler = trap(signal) do
quit!
old_handler.call if old_handler.respond_to?(:call)
end
end
set :traps, false
end
end
# Dynamically defines a method on settings.
def define_singleton(name, content = Proc.new)
singleton_class.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)
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)
# Because of self.options.host
host_name(options.delete(:host)) if options.key?(:host)
# Pass Mustermann opts to compile()
route_mustermann_opts = options.key?(:mustermann_opts) ? options.delete(:mustermann_opts) : {}.freeze
options.each_pair { |option, args| send(option, *args) }
pattern = compile(path, route_mustermann_opts)
method_name = "#{verb} #{path}"
unbound_method = generate_method(method_name, &block)
conditions, @conditions = @conditions, []
wrapper = block.arity != 0 ?
proc { |a, p| unbound_method.bind(a).call(*p) } :
proc { |a, p| unbound_method.bind(a).call }
[ pattern, conditions, wrapper ]
end
def compile(path, route_mustermann_opts = {})
Mustermann.new(path, **mustermann_opts.merge(route_mustermann_opts))
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 : {}
options = {
img_src: "'self' data:",
font_src: "'self'"
}.merge options
protect_session = options.fetch(:session) { sessions? }
options[:without_session] = !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 session_store, 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
# Force data to specified encoding. It defaults to settings.default_encoding
# which is UTF-8 by default
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
def force_encoding(*args) settings.force_encoding(*args) end
reset!
set :environment, (ENV['APP_ENV'] || 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 :session_store, Rack::Session::Cookie
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].map { |t| "application/#{t}" }
settings.add_charset << /^text\//
set :mustermann_opts, {}
# 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_server, nil
set :handler_name, nil
set :traps, true
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)
set :quiet, false
ruby_engine = defined?(RUBY_ENGINE) && RUBY_ENGINE
if ruby_engine == 'macruby'
server.unshift 'control_tower'
else
server.unshift 'reel'
server.unshift 'puma'
server.unshift 'mongrel' if ruby_engine.nil?
server.unshift 'thin' if ruby_engine != 'jruby'
server.unshift 'trinidad' if ruby_engine == 'jruby'
end
set :absolute_redirects, true
set :prefixed_redirects, false
set :empty_path_info, nil
set :strict_paths, true
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].to_i}.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:
#{Rack::Utils.escape_html(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 :app_file, nil
def self.register(*extensions, &block) #:nodoc:
added_methods = extensions.flat_map(&:public_instance_methods)
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 class scope.
def self.new(base = Base, &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-2.0.8.1/lib/sinatra/images/ 0000775 0000000 0000000 00000000000 13603175240 0016776 5 ustar 00root root 0000000 0000000 sinatra-2.0.8.1/lib/sinatra/images/404.png 0000664 0000000 0000000 00000044715 13603175240 0020026 0 ustar 00root root 0000000 0000000 PNG
IHDR , ) ҈ IIDATxA 0p@
L妀 !HH? AB@B>vΰGqLѭBd0WS>T]ή%vu W?+%T(=Cn]!3FO`Irfm `o/QܱA'tDvK1x;dOB%Lh {eƛ;'>Y]'sïFNsIݐ/
@-Y](B}dpL2Ah_V ~nF]'YKMDf\~S0!ڔeQy+%_1
kLft9g9W`!>aDH`$<#aXY^|jnqV7Tz
Lf{a⒟r%L8ILB.¸p3q8YQڒMtw@hz|ԫ$1;@32M# >Y,vçg0P\ۧQm,㰾aJ!T#x>u ~:}wcPZyR{S4!)G2w̱26˪ Ci\,a,ϛ!!&=9qF Hsea(\4W@[u60@ii+'_/Pt ܛ|L*OmJNKB&u~l6]C5
V2{(,8
Ui@n/W
36~tH,]kNV'=xӃh0SŧF\zhB ӊ~)9[HB\;9Rlc?v$ 顥qwͲ@ȪAS_}H` D1S;9ן^!п{B9%Xs= !1L}H,zh{38!?y*dM@HOFuQ`/{
t(qtA҈U\AIi(I
U*]J,p$(6I3<`ό_#YWΌ@p?!z+]fᄟJG VrA.l/^흘(
e4bbNB!@zNok7#_"(bq6B" ET$OL'(09sO~v/~]Ǻ{>çO~`V ] !.Nw-K wp@W+׃Ep(:`{gff~wɩB4xuݿ~}O?|ܽBC u\vk@Xцk3[_-nIϿxwK(Zֶ퇛7oذ۷odz{Ǐ|BJ*R%M6Yf@b)$ΆJwn>kmƍoBtzX*;5%ĉwޅrDb$TtJd~suzLbsCdlgW! ]n*i!&"x\2xs2ћ7]?8x'OcwBNkG dh`:G7t^xLO&=^2JQ`wm̡+ϏLO#gg6%` O`l67::z/[PNIX A&iAzL}E<[Stp5Ro.[e|a[@ DKKK48?11>6O& 5Cxv, ݽeuuH$b|~4"?13jhEan97ڹND\KAҌ3RP劋0R5)`N>d2U=C4754$\$AfX)In>SM\
̐
3G<@&bC ZSꉳJٹ\>!eOě|_.Ɋ9X9-U*Ŀ3x}; C F}-6éTk "|.1-. x.偺Bf$0>b.LVZ%~'pUuTB~EZ<?"6' "Bl:NDիWq]* 'H#?3¡%%(AR $t5i6a@d%PR$!G O-d:=p+pHUq(_5D