pax_global_header00006660000000000000000000000064136031752400014513gustar00rootroot0000000000000052 comment=a4dd24add24f2dd0e7299b9e68e12038138294d3 sinatra-2.0.8.1/000077500000000000000000000000001360317524000133225ustar00rootroot00000000000000sinatra-2.0.8.1/.gitignore000066400000000000000000000002671360317524000153170ustar00rootroot00000000000000# 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.sh000077500000000000000000000007061360317524000152520ustar00rootroot00000000000000#!/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.yml000066400000000000000000000013611360317524000154340ustar00rootroot00000000000000--- 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/.yardopts000066400000000000000000000001601360317524000151650ustar00rootroot00000000000000--readme README.md --title 'Sinatra API Documentation' --charset utf-8 --markup markdown 'lib/**/*.rb' - '*.md' sinatra-2.0.8.1/AUTHORS.md000066400000000000000000000070051360317524000147730ustar00rootroot00000000000000Sinatra 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.md000066400000000000000000001765771360317524000151620ustar00rootroot00000000000000## 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.md000066400000000000000000000116001360317524000155510ustar00rootroot00000000000000# 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/Gemfile000066400000000000000000000032721360317524000146210ustar00rootroot00000000000000# 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/LICENSE000066400000000000000000000022241360317524000143270ustar00rootroot00000000000000The 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.md000066400000000000000000000026601360317524000153720ustar00rootroot00000000000000# 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.md000066400000000000000000002466431360317524000152070ustar00rootroot00000000000000# Sinatra [![Build Status](https://secure.travis-ci.org/sinatra/sinatra.svg)](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
Abhängigkeit Eine der folgenden Bibliotheken: RDiscount, RedCarpet, BlueCloth, kramdown oder maruku
Dateierweiterungen .markdown, .mkd und .md
Beispiel markdown :index, :layout_engine => :erb
Da man aus den Markdown-Templates heraus keine Ruby-Methoden aufrufen und auch keine locals verwenden kann, wird man Markdown üblicherweise in Kombination mit anderen Renderern verwenden wollen: ```ruby erb :overview, :locals => { :text => markdown(:einfuehrung) } ``` Beachte, dass man die `markdown`-Methode auch aus anderen Templates heraus aufrufen kann: ```ruby %h1 Gruß von Haml! %p= markdown(:Grüße) ``` Da man Ruby nicht von Markdown heraus aufrufen kann, können auch Layouts nicht in Markdown geschrieben werden. Es ist aber möglich, einen Renderer für die Templates zu verwenden und einen anderen für das Layout, indem die `:layout_engine`-Option verwendet wird. #### Textile Templates
Abhängigkeit RedCloth
Dateierweiterung .textile
Beispiel textile :index, :layout_engine => :erb
Da man aus dem Textile-Template heraus keine Ruby-Methoden aufrufen und auch keine locals verwenden kann, wird man Textile üblicherweise in Kombination mit anderen Renderern verwenden wollen: ```ruby erb :overview, :locals => { :text => textile(:einfuehrung) } ``` Beachte, dass man die `textile`-Methode auch aus anderen Templates heraus aufrufen kann: ```ruby %h1 Gruß von Haml! %p= textile(:Grüße) ``` Da man Ruby nicht von Textile heraus aufrufen kann, können auch Layouts nicht in Textile geschrieben werden. Es ist aber möglich, einen Renderer für die Templates zu verwenden und einen anderen für das Layout, indem die `:layout_engine`-Option verwendet wird. #### RDoc Templates
Abhängigkeit rdoc
Dateierweiterung .rdoc
Beispiel textile :README, :layout_engine => :erb
Da man aus dem RDoc-Template heraus keine Ruby-Methoden aufrufen und auch keine locals verwenden kann, wird man RDoc üblicherweise in Kombination mit anderen Renderern verwenden wollen: ```ruby erb :overview, :locals => { :text => rdoc(:einfuehrung) } ``` Beachte, dass man die `rdoc`-Methode auch aus anderen Templates heraus aufrufen kann: ```ruby %h1 Gruß von Haml! %p= rdoc(:Grüße) ``` Da man Ruby nicht von RDoc heraus aufrufen kann, können auch Layouts nicht in RDoc geschrieben werden. Es ist aber möglich, einen Renderer für die Templates zu verwenden und einen anderen für das Layout, indem die `:layout_engine`-Option verwendet wird. #### 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
Abhängigkeit coffee-script und eine Möglichkeit JavaScript auszuführen.
Dateierweiterung .coffee
Beispiel coffee :index
#### Stylus Templates
Abhängigkeit Stylus und eine Möglichkeit JavaScript auszuführen .
Dateierweiterung .styl
Beispiel stylus :index
Um Stylus-Templates ausführen zu können, müssen `stylus` und `stylus/tilt` zuerst geladen werden: ```ruby require 'sinatra' require 'stylus' require 'stylus/tilt' get '/' do stylus :example end ``` #### Yajl Templates
Abhängigkeit yajl-ruby
Dateierweiterung .yajl
Beispiel yajl :index, :locals => { :key => 'qux' }, :callback => 'present', :variable => 'resource'
Die Template-Quelle wird als Ruby-String evaluiert. Die daraus resultierende json Variable wird mit Hilfe von `#to_json` umgewandelt: ```ruby json = { :foo => 'bar' } json[:baz] = key ``` Die `:callback` und `:variable` Optionen können mit dem gerenderten Objekt verwendet werden: ```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.md000066400000000000000000002465641360317524000152300ustar00rootroot00000000000000# Sinatra [![Build Status](https://secure.travis-ci.org/sinatra/sinatra.svg)](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
Dependencias RDiscount, RedCarpet, BlueCloth, kramdown o maruku
Extensiones de Archivo .markdown, .mkd y .md
Ejemplo markdown :index, :layout_engine => :erb
No es posible llamar métodos desde markdown, ni pasarle locales. Por lo tanto, generalmente 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
Dependencias CoffeeScript y un mecanismo para ejecutar javascript
Extensiones de Archivo .coffee
Ejemplo coffee :index
#### Plantillas Stylus
Dependencias Stylus y un mecanismo para ejecutar javascript
Extensiones de Archivo .styl
Ejemplo stylus :index
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.md000066400000000000000000002363541360317524000152240ustar00rootroot00000000000000# 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

Dépendances

Au choix : RDiscount, RedCarpet, BlueCloth, kramdown, maruku
Extensions de fichier .markdown, .mkd et .md
Exemple markdown :index, :layout_engine => :erb
Il n’est pas possible d’appeler des méthodes depuis markdown, ni de lui passer 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
Dépendances CoffeeScript et un moyen d'exécuter javascript
Extensions de fichier .coffee
Exemple coffee :index
#### Templates Stylus
Dépendances Stylus et un moyen d'exécuter javascript
Extensions de fichier .styl
Exemple stylus :index
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.md000066400000000000000000000455301360317524000152230ustar00rootroot00000000000000# 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.md000066400000000000000000002754671360317524000152170ustar00rootroot00000000000000# 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 テンプレート
依存 次の何れか: RDiscount, RedCarpet, BlueCloth, kramdown, maruku
ファイル拡張子 .markdown, .mkd and .md
markdown :index, :layout_engine => :erb
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 テンプレート
依存 Slim Lang
ファイル拡張子 .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からはRubyを呼ぶことができないので、Creoleで書かれたレイアウトを使うことはできません。しかしながら、`:layout_engine`オプションを渡すことでテンプレートのものとは異なるレンダリングエンジンをレイアウトのために使うことができます。 #### MediaWiki テンプレート
依存 WikiCloth
ファイル拡張子 .mediawiki および .mw
mediawiki :wiki, :layout_engine => :erb
MediaWikiのテンプレートは直接メソッドから呼び出したり、ローカル変数を通すことはできません。それゆえに、通常は別のレンダリングエンジンと組み合わせて利用します。 ```ruby erb :overview, :locals => { :text => mediawiki(:introduction) } ``` ノート: 他のテンプレートから部分的に`mediawiki`メソッドを呼び出すことも可能です。 #### CoffeeScript テンプレート
依存 CoffeeScript および JavaScriptの起動方法
ファイル拡張子 .coffee
coffee :index
#### Stylus テンプレート
依存 Stylus および JavaScriptの起動方法
ファイル拡張子 .styl
stylus :index
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.md000066400000000000000000002475251360317524000152300ustar00rootroot00000000000000# 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 템플릿
의존성 RDiscount, RedCarpet, BlueCloth, kramdown, maruku 중 아무거나
파일 확장 .markdown, .mkd, .md
예제 markdown :index, :layout_engine => :erb
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 템플릿
의존성 CoffeeScript 자바스크립트 실행법
파일 확장자 .coffee
예제 coffee :index
#### Stylus 템플릿
의존성 Stylus 자바스크립트 실행법
파일 확장자 .styl
예제 stylus :index
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.md000066400000000000000000002443751360317524000165550ustar00rootroot00000000000000# സിനാട്ര [![gem വേർഷൻ ](https://badge.fury.io/rb/sinatra.svg)](http://badge.fury.io/rb/sinatra) [![ബിൽഡ് സ്റ്റാറ്റസ്](https://secure.travis-ci.org/sinatra/sinatra.svg)](https://travis-ci.org/sinatra/sinatra) [![SemVer](https://api.dependabot.com/badges/compatibility_score?dependency-name=sinatra&package-manager=bundler&version-scheme=semver)](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
Dependency Anyone of: RDiscount, RedCarpet, BlueCloth, kramdown, maruku
File Extensions .markdown, .mkd and .md
Example markdown :index, :layout_engine => :erb
It is not possible to call methods from Markdown, nor to pass locals to it. You therefore will usually use it in combination with another rendering engine: ```ruby erb :overview, :locals => { :text => markdown(:introduction) } ``` Note that you may also call the `markdown` method from within other templates: ```ruby %h1 Hello From Haml! %p= markdown(:greetings) ``` Since you cannot call Ruby from Markdown, you cannot use layouts written in Markdown. However, it is possible to use another rendering engine for the template than for the layout by passing the `:layout_engine` option. #### Textile Templates
Dependency RedCloth
File Extension .textile
Example textile :index, :layout_engine => :erb
It is not possible to call methods from Textile, nor to pass locals to it. You therefore will usually use it in combination with another rendering engine: ```ruby erb :overview, :locals => { :text => textile(:introduction) } ``` Note that you may also call the `textile` method from within other templates: ```ruby %h1 Hello From Haml! %p= textile(:greetings) ``` Since you cannot call Ruby from Textile, you cannot use layouts written in Textile. However, it is possible to use another rendering engine for the template than for the layout by passing the `:layout_engine` option. #### RDoc Templates
Dependency RDoc
File Extension .rdoc
Example rdoc :README, :layout_engine => :erb
It is not possible to call methods from RDoc, nor to pass locals to it. You therefore will usually use it in combination with another rendering engine: ```ruby erb :overview, :locals => { :text => rdoc(:introduction) } ``` Note that you may also call the `rdoc` method from within other templates: ```ruby %h1 Hello From Haml! %p= rdoc(:greetings) ``` Since you cannot call Ruby from RDoc, you cannot use layouts written in RDoc. However, it is possible to use another rendering engine for the template than for the layout by passing the `:layout_engine` option. #### 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
Dependency CoffeeScript and a way to execute javascript
File Extension .coffee
Example coffee :index
#### Stylus Templates
Dependency Stylus and a way to execute javascript
File Extension .styl
Example stylus :index
Before being able to use Stylus templates, you need to load `stylus` and `stylus/tilt` first: ```ruby require 'sinatra' require 'stylus' require 'stylus/tilt' get '/' do stylus :example end ``` #### Yajl Templates
Dependency yajl-ruby
File Extension .yajl
Example yajl :index, :locals => { :key => 'qux' }, :callback => 'present', :variable => 'resource'
The template source is evaluated as a Ruby string, and the resulting json variable is converted using `#to_json`: ```ruby json = { :foo => 'bar' } json[:baz] = key ``` The `:callback` and `:variable` options can be used to decorate the rendered object: ```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.md000066400000000000000000002323351360317524000146110ustar00rootroot00000000000000# Sinatra [![Gem Version](https://badge.fury.io/rb/sinatra.svg)](http://badge.fury.io/rb/sinatra) [![Build Status](https://secure.travis-ci.org/sinatra/sinatra.svg)](https://travis-ci.org/sinatra/sinatra) [![SemVer](https://api.dependabot.com/badges/compatibility_score?dependency-name=sinatra&package-manager=bundler&version-scheme=semver)](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
Dependency Anyone of: RDiscount, RedCarpet, BlueCloth, kramdown, maruku commonmarker pandoc
File Extensions .markdown, .mkd and .md
Example markdown :index, :layout_engine => :erb
It is not possible to call methods from Markdown, nor to pass locals to it. You therefore will usually use it in combination with another rendering engine: ```ruby erb :overview, :locals => { :text => markdown(:introduction) } ``` Note that you may also call the `markdown` method from within other templates: ```ruby %h1 Hello From Haml! %p= markdown(:greetings) ``` Since you cannot call Ruby from Markdown, you cannot use layouts written in Markdown. However, it is possible to use another rendering engine for the template than for the layout by passing the `:layout_engine` option. #### Textile Templates
Dependency RedCloth
File Extension .textile
Example textile :index, :layout_engine => :erb
It is not possible to call methods from Textile, nor to pass locals to it. You therefore will usually use it in combination with another rendering engine: ```ruby erb :overview, :locals => { :text => textile(:introduction) } ``` Note that you may also call the `textile` method from within other templates: ```ruby %h1 Hello From Haml! %p= textile(:greetings) ``` Since you cannot call Ruby from Textile, you cannot use layouts written in Textile. However, it is possible to use another rendering engine for the template than for the layout by passing the `:layout_engine` option. #### RDoc Templates
Dependency RDoc
File Extension .rdoc
Example rdoc :README, :layout_engine => :erb
It is not possible to call methods from RDoc, nor to pass locals to it. You therefore will usually use it in combination with another rendering engine: ```ruby erb :overview, :locals => { :text => rdoc(:introduction) } ``` Note that you may also call the `rdoc` method from within other templates: ```ruby %h1 Hello From Haml! %p= rdoc(:greetings) ``` Since you cannot call Ruby from RDoc, you cannot use layouts written in RDoc. However, it is possible to use another rendering engine for the template than for the layout by passing the `:layout_engine` option. #### 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
Dependency CoffeeScript and a way to execute javascript
File Extension .coffee
Example coffee :index
#### Stylus Templates
Dependency Stylus and a way to execute javascript
File Extension .styl
Example stylus :index
Before being able to use Stylus templates, you need to load `stylus` and `stylus/tilt` first: ```ruby require 'sinatra' require 'stylus' require 'stylus/tilt' get '/' do stylus :example end ``` #### Yajl Templates
Dependency yajl-ruby
File Extension .yajl
Example yajl :index, :locals => { :key => 'qux' }, :callback => 'present', :variable => 'resource'
The template source is evaluated as a Ruby string, and the resulting json variable is converted using `#to_json`: ```ruby json = { :foo => 'bar' } json[:baz] = key ``` The `:callback` and `:variable` options can be used to decorate the rendered object: ```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.md000066400000000000000000003040001360317524000156210ustar00rootroot00000000000000# 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
Dependência Anyone of: RDiscount , RedCarpet , BlueCloth , kramdown , maruku
Extensão do Arquivos .markdown, .mkd and .md
Exemplo markdown :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 => 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
Dependência CoffeeScript and a way to execute javascript
Extensão do Arquivo .coffee
Exemplo coffee :index
#### Stylus Templates
Dependência Stylus and a way to execute javascript
Extensão do Arquivo .styl
Exemplo stylus :index
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.md000066400000000000000000000424511360317524000156520ustar00rootroot00000000000000# 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.md000066400000000000000000003626561360317524000152500ustar00rootroot00000000000000# Sinatra [![Build Status](https://secure.travis-ci.org/sinatra/sinatra.svg)](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 шаблоны
Зависимости Любая из библиотек: RDiscount, RedCarpet, BlueCloth, kramdown, maruku
Расширения файлов .markdown, .mkd and .md
Пример markdown :index, :layout_engine => :erb
В Markdown невозможно вызывать методы или передавать локальные переменные. По этой причине вам, скорее всего, придётся использовать этот шаблон совместно с другим шаблонизатором: ```ruby erb :overview, :locals => { :text => markdown(:introduction) } ``` Обратите внимание на то, что вы можете вызывать метод `markdown` из других шаблонов: ```ruby %h1 Hello From Haml! %p= markdown(:greetings) ``` Вы не можете вызывать Ruby код код из Markdown, соответственно вы не можете использовать лэйауты на Markdown. Тем не менее, существует возможность использовать один шаблонизатор для отображения шаблона, а другой для лэйаута при помощи опции `:layout_engine`. #### Textile шаблоны
Зависимости RedCloth
Расширения файлов .textile
Пример textile :index, :layout_engine => :erb
В Textile невозможно вызывать методы или передавать локальные переменные. Следовательно, вам, скорее всего, придётся использовать данный шаблон совместно с другим шаблонизатором: ```ruby erb :overview, :locals => { :text => textile(:introduction) } ``` Обратите внимание на то, что вы можете вызывать метод `textile` из других шаблонов: ```ruby %h1 Hello From Haml! %p= textile(:greetings) ``` Вы не можете вызывать Ruby код код из Textile, соответственно вы не можете использовать лэйауты на Textile. Тем не менее, существует возможность использовать один шаблонизатор для отображения шаблона, а другой для лэйаута при помощи опции `:layout_engine`. #### RDoc шаблоны
Зависимости RDoc
Расширения файлов .rdoc
Пример rdoc :README, :layout_engine => :erb
В RDoc невозможно вызывать методы или передавать локальные переменные. Следовательно, вам, скорее всего, придётся использовать этот шаблон совместно с другим шаблонизатором: ```ruby erb :overview, :locals => { :text => rdoc(:introduction) } ``` Обратите внимание на то, что вы можете вызывать метод `rdoc` из других шаблонов: ```ruby %h1 Hello From Haml! %p= rdoc(:greetings) ``` Вы не можете вызывать Ruby код код из RDoc, соответственно вы не можете использовать лэйауты на RDoc. Тем не менее, существует возможность использовать один шаблонизатор для отображения шаблона, а другой для лэйаута при помощи опции `:layout_engine`. #### 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 шаблоны
Зависимости CoffeeScript и способ запускать JavaScript
Расширения файлов .coffee
Пример coffee :index
#### Stylus шаблоны
Зависимости Stylus и способ запускать JavaScript
Расширение файла .styl
Пример stylus :index
Перед тем, как использовать шаблоны 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.md000066400000000000000000002226731360317524000152350ustar00rootroot00000000000000# 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
是否使用布局 (truefalse)。 如果使用一个符号类型的值,则是用于明确使用的模板。例如: 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 模板
依赖项 下列任一: RDiscount, RedCarpet, BlueCloth, kramdown, maruku
文件扩展名 .markdown, .mkd and .md
例子 markdown :index, :layout_engine => :erb
不能在 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 模板
依赖项 Slim Lang
文件扩展名 .slim
例子 slim :index
#### 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 模板
依赖项 CoffeeScript 以及一种 执行 JavaScript 的方式
文件扩展名 .coffee
例子 coffee :index
#### Stylus 模板
依赖项 Stylus 以及一种 执行 JavaScript 的方式
文件扩展名 .styl
例子 stylus :index
在使用 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.md000066400000000000000000000036341360317524000151630ustar00rootroot00000000000000# 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/Rakefile000066400000000000000000000155511360317524000147760ustar00rootroot00000000000000require '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.md000066400000000000000000000055041360317524000151170ustar00rootroot00000000000000# 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/VERSION000066400000000000000000000000101360317524000143610ustar00rootroot000000000000002.0.8.1 sinatra-2.0.8.1/examples/000077500000000000000000000000001360317524000151405ustar00rootroot00000000000000sinatra-2.0.8.1/examples/chat.rb000077500000000000000000000024571360317524000164170ustar00rootroot00000000000000#!/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.rb000077500000000000000000000001331360317524000167560ustar00rootroot00000000000000#!/usr/bin/env ruby -I ../lib -I lib require 'sinatra' get('/') { 'this is a simple app' } sinatra-2.0.8.1/examples/stream.ru000066400000000000000000000010641360317524000170040ustar00rootroot00000000000000# 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/000077500000000000000000000000001360317524000140705ustar00rootroot00000000000000sinatra-2.0.8.1/lib/sinatra.rb000066400000000000000000000000611360317524000160530ustar00rootroot00000000000000require 'sinatra/main' enable :inline_templates sinatra-2.0.8.1/lib/sinatra/000077500000000000000000000000001360317524000155315ustar00rootroot00000000000000sinatra-2.0.8.1/lib/sinatra/base.rb000066400000000000000000001756141360317524000170060ustar00rootroot00000000000000# 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/000077500000000000000000000000001360317524000167765ustar00rootroot00000000000000sinatra-2.0.8.1/lib/sinatra/images/404.png000066400000000000000000000447151360317524000200260ustar00rootroot00000000000000PNG  IHDR,)҈IIDATxA0p@ L妀 !HH?  AB@B>vΰGqLѭBd0WS>T]ή%vu W?+%T(=Cn] !3FO`Irfm`o/QܱA'tDvK1x;dOB%Lh {eƛ;'>Y]'sïF NsIݐ/ @-Y](B}dpL2Ah_V ~nF]'YKMDf\~S0!ڔeQy+%_1 kLft9g9W` !>aDH`$<#aXY^|jnqV7T z ˆ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Ͳ@ȪA S_}H` D1S;9ן^!п{B9%Xs= !1L}H,zh{38!?y*dM@HO޵FuQ`/{ t( qtA҈U\AIi(I U*]J,p$(6I 3<`ό_#YWΌ@p?!z+]fᄟJG VrA.l/^흘( e4bbNB!@zNok7 #_"(bq6B" ET$OL'(09sO~v/~]Ǻ{>çO~`V ] !.Nw-—Kwp@W+׃Ep(:`{gff~wɩB4xuݿ~}O?|ܽBC u\vk@Xцk3[_-nIϿxwK(Zֶ퇛7oذ޽۷odz{Ǐ|BJ*R%M6Yf@b)$ΆJwn> k׮mƍ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%~'pUu TB~EZ<?"6' "Bl:NDիWq]*'H#?3¡%%(AR $t5i6a@„d%PR$!G O-d:=p +pHUq (_5 D(F$ +T"Mvtlڴ h/HTMNM"/Y$O Ūw` +CJK^Mitޓۢ,_=\iU w%Oy~7o^n;?޶m+V@kE28-gl8^+u*[2$&bNt9ru&Sbb-+VΝ;n rxIN~ғBDL_:::`nUbP$oU~aDɟK#c-p\8LXFsw;mۏ2#w{{_ƍl&Gqhh諯Nwi;v ו9+N$: 5_9x2Ҁ딨$ąNMi*Nt֭~tԩ#G%n}_ӟ?v7ǎݽ{7_>|}uIwA\[`}xLHEb(5LN|'W^]~F)(jՁPioowzΕ5:t$F-|b+I`2D}g-(5]Ȓ8%PsrEn~H!EO7Uܓ'{Ē[x ԏFEmj<,n'Tt 4H :mٲe͚5𦭚h΄bZi/bBS"XN#LONb<@2p/vvvoCsX$[]Qgy肹{{A et Q NSZH-p(Wٳk.zK|tJh_p2|]@ȷX VxJv0-QШ"ȉIJ%8yk&y4m˃n?lkz(B5("HF@q#K a%|xDqJZ셒}QnJ1 e%tbW p88vu5X('mU! hdIOAbsK \Ξ.FPU]mMN5>jѝyBr~m󪁐0)nZ0;z4*~'h FJEL! w&Bxu~^+ʄ\SPr1fPF3O?XNV#,O2eY%yEvG;o< bnXf*Ą_~񇧧.^HK{&] dw~X!fB<'mz=2ev_]\h#~}i3KXo:aE# GZ۝kz9޿Y~*wʽr?E-{EqF@(琠{5}r\?"8#5heumuR.O] W MR0Auݭ}NGuTrC_.(h /$jk! 3I qCAC} 2Q̑i  uE/PMUTNs yV,kPz4ʀb㶫 #>zBr5^ݟ/A05P 18QC㮚Wi]42$ZܶI~!`xfU(]P<]8/*86<~0't"?|%nj!c]l?k$B)؇v_܏;3B_IρLkm;ad1N}kdTJ5N]ؒSs` Tj_Ns7B+9ni^з~̵Ԧ1kC{8q_.]b 1A}/}n߲ +TC<*>=ai=o.:k{AHNx^L·*B{4XMa^G8hRφ%/Dt&lOP .E4~x(X>ޙnOqT];d`LGB[(}v6 *bo7 p^XWˇm&^6Ey0)R2縷[μsĜg!#ߩ g9p#^XVH]4͗/WNEE.YGw {GE2AM,W%tԨ{dHtrt6-w r#?}{{w;lZLqǾ LM3G&VX@`'& `g!nƢ[#O\O8B{,iPY6 zBxTv|B/*\;gY]~7ǝ,QMeWPxx}1|{!lq p%n ݭ\/7 gc0Eខ;Y}R]ŷfB,v+9Y~y$( t7S7ĊXRn堔uB5;4cmZ-Ū4y!Ne 2`X? ]?:d"59/ z.9~.u~Rp w]:m¤J ʌQ[ ˚=B|eUf8w}VBǕ%ZU`'N__Jh|&ںVns4-ik2;=Σvkh˸>&ljh6΄LԶV԰i!ɉY4sCl8[Cn*qaB嫎 9o~OOC%gw;QLkә9/H{dyZW܎i=atwzLB{RC9뒲} u(>K@֬%'^Qg;6 XR/s!Gd!٦ 2ǜRܝ+o!pm2qǺjMU$ICn]цcx':DON|U"?t:,{DTsh޷9=br 6G|Bh|$!m(> x\ߨ*~I?i#RTwͻEJ+-Nzޣ`0bN7&CN`Yugnhh5뫜$N{'axn;A9 sbC,0 :>`{$DXBEtwѝz_?_g~GoA~4$AE{}%!Su]g1t\O ÿmڟ6hܖp7XwQywqWOT(Q`f|O Ӗ /s#Nu`C먏` ̹}|u?W'm=?'wgQ'LUB. L}k"W?[o="F}} 5pcd@~ș~8cC6].ǷA'6i*-zl`ňǎ~yBwb E{A}L7w>:<1ݰq򅶖8y&>bBy-(q5e2 cEa&3kM{y[A1ZdݎU ?^<~y2RVwdL79?ă}5՗<Ѻ7unnސRUxV򳭣/a61J,ͭkş>wd"5JB{Z7JyB<=׎{ƣǒ+ba.#ƽD(x(N=z֏qu1F+S&T?!?fp@oUbg)r::~}M]]cHw1L(?hC/.2c]TYw]fkmFz0X\ NRd ETE:O'4Ȝ:+1 ,pb·pʻh0[;ՆN.׫O KIWtF-rEYw"fD,Zoo ݋ծ4 ӎGX's/%&6U!$v s{*a+EJ.c{/i<~qmW[8^luE4^Хpxߧz<[?@?,CIQɓ'췆Tj/9!t߁A3=S.u>+%[hYvGm!]X׿>̏ ,cq/٧3N_} bm[n=W[곘0tH˓{{jK{rKQz<eRZ8nxFؚO<[.1#+cEQ&f;ܫ.]O+"=mݵH+ُ_NBNv%Ӧt_y/2Ď.QdWVvדٕ6śDýwEn +:sfF'qt. zl%[8t3g֒'g-"nYCѶtW9Gɇc7D+*B UP)^}wuޫyl`]9ED?<͙S@ OVW=!w{̴Xk-rҋ^~UsA1W"zwv"'^t 6:WztoLFEʄ 02 돋$qL u(!'Ov}`&MXT5 ٥ ӃH643${^_Xzȿ>v3隝UzN2ӭ59Kҽ=]gP MgckF]s+ޭߪKeT`O0LC+E}M raLB}%%*׮o+}&27?f ou(څJVE%]bx"׭(s,1$? S_o|>m59IPjHת㕯ftjHdΟ~Nv\=׶?.R**Uu^Ĺ3z;*+fҧꄴ#2銘Jd2O wy>avQ\J{ûr4V\{܃Pcv>؅gl]t])H' wtq(=p]9qqy>ug:H+^'XO(Y!cr|b CC7,/z>ehq'wQשKs%**e^݅\"k/̣isvҟl~xy$8S(n7*~~d\c1y&? +#OpIiqC[,#FMŞuw52 CI& v\^'ħ Tx MJcS{{#wGcKf?S0mz֣K$^S 3헂\Z_h#G+,8D׎{0nk4x{{O)?v4ٻ-rc9(8l0'OPb0_ ЛIΥ=x̹y6b|F8oM1wa.ܾic d7 a,ܽå|({ΕHlV4e<͂oxҹ>g;f(!Fa9Fm׃P1:IUR0dK@m $h`q q!q@<9׋AȝfK ^>;wP=*NN8O(I1v4֛>~I;ۈd3!w΋C|]եW|19ڻAw]ץtY-ת8;6^ BCq\W0/7TR:b1dGR\f {tOZlh}GtAގ<:?`⶜%=>i;l.6xV2ׅ -m |dPsF,.{Ò&2!0T腕|BT#@!m #Ey t2+·n2PfBPt89wTHoBjeddgL}g{֩DO|z+z;z'sp%ERzUG@ၐRbX^K~?kzOYp.;Zi{i}KdõPL{PKWТug#1<TP+i=B}y)$N$ ɇI \׭O_qK_w9n iKSm* 2pԏKrd@_QZ'!qR?4ud>L.!̰r@#D`h| 㭎gp/3gpn< %?ǸojlƪBQ?u6L?ḷ  b |nˑu^u(|mPwؕn#H:>OT)D(&?pZJ:߾,8%iɨ3R* $`pShN-ޗf6ր_uGmU%RoQSdff̙$vAž. aezT7f:OGl3.r4/NzX%^up՟ܾh~OL<ik0_S8Uʟj5,ߡ)~܁l6M1s`zMAvRSUeV|a~SF>>wɨpzw$eZͣ1*)J|`TKHY*>.bP %Q}o6w|K< -e"cߣ CkyFg-X ;+̇q쭁`\ @W? k4ӊzb< SVpV@ &i^{rS47޿7 E.X]e9*p8+XVX"%JMA =pf/cEe LlЩ.b@3Q2;N.#br>3]p.bsluݲq SiԿN7$*fRa"nKjӯP)Ir+{b#l]-iV|Y,3Lat[<:tu~##~DФ(\.8·Z.TFL>dMHdeJݨ~wE0 .3Y߬T ؔLYRCSeF ¨l 8pzI0pB|Ġ w"sePwje@݈oʋ*ApsB@¿\&h󪗮]ޑ5uZ<'YPa-M+%ɰ?WnAtgX 'BFGGFrƥ2~65O]8],B7ˊ24#U+ z*\pM2ǯ{u8Oh3#C;i!\\&ɕU"=a^%6}"n#@bIeM~uMO#v}28"X;J<~|[}E]M5~7-GaðWl&A9du1\&w@dpH wxNG:s@Kq}orcw 7)eT >j687 LݦGB1?9vDe! xhl WJЇ_->KMlfX@ȐMy]690F ɐLlD8ăoZQKMw!,cFq]ᨘc# `b(G.j*e@4O A^xG5侀`- j 5 @P94jhC3Voӫ9{>bc|}Ι9ܛf HR?d2Pt &嵣L$M^x̎<RP[}$ty\/'Sd|S LS/ALx@D r^yіc9rs!9!N>|Ǐܹsn ى7#lWP_~lf:--%!cʀ2!0P ER y3<Үp8r=}?_lw+..11\p>U]{lEyw^sSy&UQAL~fWju#:fB `|& I((mK%qI7%cc  h _b(U )a@'N45k*jީ]z͂ =6ݻx{_ %%o%o%j^+}O _ )LEQIciJ"Ε3 1 U'@ T,[q2 \ -4\ Y/PjlJ)V-ji{KD $LBHN͠$$"kM¶enUZk,>Y>:vR: ~0z$0::zۉsff5SCt\/rFp|?La,p[<&ٟj7LLB=+Mѓf$pp?Aw3BҒFw鳟}dIq6sSL&p86CoUŚ v1 4&]!}5Ku|wppZ#T4<qL< P--g *Fg$9P3)eOesη:,`b*kF٧r!dNL1'ĢPV#} 2B)8?٤ @őDV9h03y@BD1 VKòJՑ^e_N9D&&!!B.ƕ6^Rv.xMJo'>ذ!iu} yb+q2o,Jl&C{ t&^7ׯ_x1oRXQ$3#$`{,'K|e ƍKPlI9 Tf3A2j~xՊA6`B=$ Τ8Eݻ5㺒ԙ&' LL8{AKnܸT8L`$*qoLW&,l1 'Ao^N&XmeJ텘޺uvgΜp8y$ڦ&=7o\[[%F;H/'y$0-{Fn h O͓P*TpDBʄsƁ%*P$mēF-7Nh>+$d BAh#7 #fJIW/D g:Bȁ-D"MW y*L7t}QC GYλLNw EK ZiiNHoXz1wqrE^s 6"|A{%>%,+g o* n[Sj;(ȌϘyrߝifӘ sQ /8ks}>*O[ia>wc] enR"t*}Xpbc9[9q>ԶBUY  F:mrZ !vTT9qZ'K?ݖ`>KPVtJu@SB97|cQBQ0%%QBQB%%QBQB%˽ѣ'T?IENDB`sinatra-2.0.8.1/lib/sinatra/images/500.png000066400000000000000000000574721360317524000200270ustar00rootroot00000000000000PNG  IHDR93_<_IDATxXUGR6MnLlM5QQM,Ecu`EƂT,ذҥ (;ؕ"5o29{.pVE<QcLt˖DG!oXOw&NꪅK_4n˵/wz0qҤf #-դ&W` WLƏݼiöXb>`@"%KZԩKnƌ;gAXXhL6mjR,:R\ڵ+)&Mar y*.~m@8::bU&ݓwDGEj>!ݻU3䉫2Y6lءi-W>&U3䉫2Y XwߟҴؽk&hfWee5X!r߂ŊI7A;\5CZϡ+:yԬU[nݻnbA&W͐'ҁڵoCPrW/^t=ƛ |><\PNE Bz=o ҵx>V=udK.A!:&ƒ'"n0|Ĉp~ƛZUar޸I; :m`&lڼU!fM ߍкMw-½^Mo2fש[[ //Wy9:\P!c["y2:}6A€e+fΞ%Bԝ23O:|ҸIק;9߹}xy@*s#O4(bm;5`]tV|@?K@۷o7o@W5|UBzȑ5oa DhgZQj၃TRK}E/Q+&Wu dq0ːJJ.E(۵uߎO\{ZzEP.Mue ՒwMIMcRwMŅ*bÆQ%⠏UnZv4k 44j+WRy]QPC (*#)4ˮIwܱ;IIH(UP\%c Yfȧ\Ё*Ni#:^~`QC_{]>!b0\7o;u4>PUc*b8\0r*/A y顸0jNYMָz~'۵;~YAC]ժH,Z٫w)t? z%<59u4 N26wud=Ȧ,BW9*eS`3NYfw\ogQs/H6}'=KEZJ*6< DDdTa *,k}|g{ DZ+M5(\6}+We$ΐ06W6.^D7~byd.S]z|M]-'N^vEg(%( )6\ @D}!၃lhcB]8Z5Xd{LOjmUdP0W*aID"%b. b=.̉7BW?eg2&6IlO8100QԂTt nKPcx(Z֨YiӶ=CؙDr"e5'+E#8#Wj"/ ԁmsO2q*Wa >\9 BY*Ӳet7߂'yG`11"sUY351L@N坏6.層36ڍ8y(qxlCvGq&Z\%J]Y'Oڜ`K)Wvaҭcp2'Mj.C~(BkUY-.< %[X9KUn})k*srUVntteQPEWu*=^m(#͜lEzDՊ2"s 9*?Q`A>4FԢx Of(WD*0K~ <}W9 >sVVnl;!t Wۿ3OשĚnD,!T$Pg)WՔUUlh²b(DmvEZnL'y, \r"^*Ҵ'OlH6KU:("1tEȋaެ]k <D9W ʼ%sxl 7amכ2Z~GXjr5_rW{c} TyUہу-ˁD#F}s7\W,Os|-^.al$@XU,]fI{+*s2LoDI!Uk OUW4vZC!u :Kؒ"Rq#"PM[jY+|9ne'__~K7i>qkM7mع!᪤,\ ))igϝz*Jh ܈+n!9gd *\Yv`ZjDEeāl5= :W6&6No3[CW&lBWTJF\p_Ԇwgm:fqL֪4x(кM[(V圫) VMXF}4`r L6AWٚ&=&Wq@nٶ#5( "QNֹ+}^f(դYK\nqB޽4:yB"l$J ;\]q`bR*)Ŀl.'zB,DN;+_7,ya^]_ $"quM,6`Í8[(m݊ /X}#~{^ùח(y Uٲug(7…ٖ:@ M T){xF'P] ֶE{4d[n'BacMz4-Z1SU W/U,OsuGa!g\1$rv֭=^z70G&%f||g٧lںQ 3GÄqkB amƒdjZZmZ;˛+\Wsz'˹2Uj ?.>*8U"oJ.^)*s"s@g ṑx@{١U Aaڵ^G Ia*\њ7ns.۵RuP3n{Ŋk䏊uxW1GcU-_1wQy.2%n29'q=gQl#CT겯۶DG.]M._iv8 d_,5i֜Kjn:rԝw *wsU[zyZ9HxD>D=Ʋ{N=K$ۯw6/"}SWuBD%S WŋhiG3kq ttW&ۇ]? o-]2<,X pcovW'k_.\ ׁ*zN:EY۾gru;qf-~وӡͷK撫#F}![QjWnFSa&8t5 D;pdՅKCZ<,;vf,hͷyt:z WYd1rB\32x v֚F C,%q%w %96U[PO계s{|i |ŵ߀w5gN a6T*VZRqG#$Sa,WnD_w8A.Դt>bVS8ԩS2:w^ǨH^̭uyP,E+_5Y+WȤz3svJ4G~h\".ض};?=6F "j{KDnc[`eʖc,#W ;ܼ 38Wg)E ΝNuÈͽ򵠆U::9,4N)S|dTk!~*[%">f L7uvĦ|tyYQԥ3Œz䌻M5;}9&L,Tt 0e˖mH~YPeSV lMϜ= T܋?F6Fx,\[ݴN`X`kG]\l蕫5m"22]0F_*vGQ?Q">|ϣaPYˮOGO.]4TJ9|MQApUF,!߳wq*(NI#jWXg#[JaD&Kę8aJKbܶ3~ݢҶ(/y,U>ΊA;:-&M̡h'_6j^߸/CP۾DQz8t \K"[G(Blo /d [%^M!r 3k}leO^T8jB͛X_pl^-$`U`do|$0rk-"q&2Wf/X8tMP"i9)5,UT}8f1$E`[π8ΛXULSʔ-KE``h0DŅ ON ta.]uPtԨrڵJD81d p#wA^,Hڳ:D3q r>5XRp'"e yxiTs0Fﴯ5~2AUlC<'NBվt^,ECfG9i"!\im^^#aimV b&O xStrqŋ{"TpWDIg9ĉu㽃TRAƱL2DǬ11cCzʁFRkO^J-_bX-&y{z>uDĖx'*\QJZ:@5/%2(d@jD t)N{H3 G32;{.]1)\=wҔün. Ea,6*B'Nnn/ɽǙwgOCg_\s5W"Q Ve|*x =_"*4#G9 $@ &7:vQAc^N:PtZT/ /)T9ixҖ;񤓓6^ZIScТeX0M-A90Sc~o K!:zj ;1 d߁7 ǿ5`5`,k(oG٬D\ҸFZEXdJCqk|Io3aR2{lYj{ºGLp?%sA)Om\|ՊUkw/l)я(e ?m쉓?qӖ7˯BЇ#F.4Ïe=)Ѡ J5jt XeyVY0F@٠ R[?PDd6 ɧemI\*|$R 5ܶAyg̚knV3Y,5$PuJfTzISjnfIL7=_EW/"J$;=zӠQ*7ܵǍX\5Xg|D!%BDf3Fy՚/XDb0{Tկً*wߗZQ_9z|>lλ%tKSN9Tʦ[,ҸI7٫ϐa# բUێJaqW~rguv 9) 4lhACϽ zI0\U~|~!Ȅ6~Ҕ!åXP~η}qǗX/mZH!~4'SsxJGѳ:u ^\;+lذI?P}}+{̚iW{L€Us*T,CK8f7l3A>{7R,M@aa|+cO<[(ha,- #OPؿ bh) Kيb7 s|EJATq*lIDPѥnةK (7bB^*g3x4=I& gyWרI ?umۍ_ٵ/; d[N V9;^e3=t]?>zDlіi*~{CGZt( ~`Ҏ]!^.﫮Vw^)(9W&d4a#F .k2\6=naH,zaa #o`?_ڹ52|y h$jD`}ը٫O=0ÎK Fq"6;w*??E nܷ߀IStd,]1z 7lb, 9EKV<kȗ}ߊCPl}bGI<ˆJ`45򼰝Y3g]{A8p0A_ι-8lT;Ie˗9dauO~zNG*WEi -]\A3w'$G D5ЫO?ΙXqݲ` QVt%N5/mX+vEEbӷʕ*+qiztcOڭ;]bwrD P"U);eXաS Ō#ގ?xITp]*[n^=x -:=7HYjKH,VݲeyUu}ԆMoq):v ~M=O~zv*&B2ڵ Wa;蛜+.]qߌf]v3mEML1wmwԭ簑c&NE:U ;1JDR&2DK~w.W)o4 uDO2E&_f ۟~?2͑ -m20;wqZB@zfK` McRgz̓QK=O͝ͷV/ qY)ܵ(c.)eϛ8٦M7IڕF!iwneh۾;s[,؂6eZ{B/ptQgvRpF6wx' hKQBOiOn+-j !Y\x'*pI!&Mbq>O 6O+%緹\(TTs!w(DܖNX'~f \B;(zfA3bڰ8Kewjݖ۪IZFpH7c3UmRгw_qU Nۡc,WɴȁvϮԥO;$iב[Dt0u1U9lݶI ]uK%ܟJ6# <Ԏvy65~~;^I}_@|19WLhDTeBlp9 4@{T.'>(/ ֚.7mz֜,5+Ғu>W\Aϙ?f+_͂Qu{#smΙnF skc*B}Fׂ$dz$-[\7.DF}SC?bΡSŻb#3Scdo*/|& d=z{)WcSD ЩY|ZdvC Ǟ=w3K!:*Ͽ+1 }98;/J*x4%x4%JpNQV'8P . F#f4 ISI dΕĖ["d8xq F[)lpd{v~rT;-[n&-'/i@H(x9o G2|tfA~] WcEsb۴mж]xI*i6dd~ʥet ʝS59ʾp{6+PVY6 )?JPxvV_ddL $Y,%O:p5O#XkA)HK20gqMdCB3֛kǯ d9t!9EQ-y@@u?i/Mpyz<1^KBnՂMBI6|i'[C&ME)нg\g!LBX S|#zA}q&+SQɠ I{ ;l'' Rdrl v*butf>,!`9"?'&`eؠɉl N !*s\e$#M%= b Brsm|v3 \bt nԪm;>˙ On(ɸ!kOZIĎZ?Cb)"V~RwC(' bYOr䨱n>8ٵ{/+`>fVYA-LXt1Y,a$A66>#>Xtf*kB{brp}hm\ՕQD܏Hx-ΔA0DEWx؈Q;POH(9:N^KMj2S)ɳ\e<C>1|G%P$1PlCONp6:VKZhQIjao9PQv:3̈w@c̿PnvSJjB.@"tl?RVA2tJ60Cng<ᖪ,]~b.O/jzbi\r_QC 43 û#TY ؊\<`”Y豿67|?з[F6d۴s-]28nw(27°SH:8C:vTJƃNѵ?Uڡ|kz1`7l SYWU*W'0RMh޲W_󳔒"VXRnLiu"&NoΜͳY}J>6AG3QrWcM+׮suJY*&!K+WC2qJ}„ݥ[BD $3;۱&rUP6_gt$"4(w񲕬M>2z -FM@E}*h#kDa>[ҕ0EN%DK)kӟW{%wp׼] X]\x)8flAW5HzVa5n*Hݐ:cHNRrIQ1$XnMgN j#ק@.\~iZ.=zfwF[A-g+1`4%pҕu_K Y#шj\`ᒉS S:G0ՁG}6:嫣2HILz+PL2$-J*o-˰d>fiIkH@ D̝rdi@a=QWevm|m'iiX`3vG{fԐož$83)ҍ44ymvAt_`Lr֭{Tt=FKKNݎ՜j׭ץ{O//[VȮFSj.,Ze+?(<8QzXxrQ"#dN!C'߉W٥<5,W/*F95ag"Wzm>*U(C3.^!|;vb !{MsΝ9\M28ŸdRE KsTd?F*{ʗѶC磄ҾcgqS+*_9CGkE5w7nu5 fO:jD1SyX1f$pcj-YέgSeS* K5.NU__L\[s-Ҏ)PdA 3":hr12_.jX&d@LO8j}UטhѺ}hԴ׾ 3 .({jML\5-[!BԤY˜\߬b//wO/iܤi$'KSI G#Q'N6l5QƺQ0VƦx#ծSU 7j2U[i1H6SG9,{\HڀX!A!OJo\p) qՅͻU<ɼ@Bf,]C4xu|3":3sΚ=[tL4 y4M=-{x}'\NxV򣇩td8G2 0,4 v૮(H#@ڟ]ݍPs-Z=,ڶHM 8L6p'nzwkg|!@cҵ$dL^!ڇIcb@0aɲU@5Haμǃƹ Qpc9g(ZjJUb-SR4GGy@Gx[`k `W` 9 |QhK EKr$|Cow~W`9_Ո 3ZéhZ*LZq&aAsR.oVCR1SE7i*vܵ'{!v#x݉ #zk|ݹ~jn qoZG)tyƕk3S$ߒ.ƨL^˯˯8 Unׁz[7ئ qq> ,1&q5;⇫MX*Ȗ1t}L]ƼEqFJp0:`H++`Z%cU aZnשslsIr!]5kնLm"1T—ކcxޏ-[5$۷2;~x޵UN.ӍK/Y턹WBxeN9JlfJu٩ z+:/猳PtWr;JzxQ XZ Z|XAoAi^[=Y!taɲIpU%Pc1BsՐuz(z׭+JR6&.JPg{?83!g0ڟYց7hԴY+@2iO;=BR_(u54*0yF)41nsޏNST],'?X[ָjMnF gk%8UD~p5J #?칎3id.7("UuYtR`i !ңb=W zKC-+ڧ׀`ccG[ 0;j.A[^eE6T_o؅N=t~:g@BRLbڕ\ײM YH^E^x%_;+r2^גW\yu mcnV׾λ7RNhҴ 'I[tOآO9'X^WF>'U&ߛv̍xc$r5?;팳R,WM_PcY Οc,At KGC)s؛I]ҏgUFL٧*&Y,?؅GUDWM1[8qsq!-@JgCZB3gt2} 꿫 K6lϒٞ=,WnEv/*H uل| w֫P.iJƟm8poY49T]'0Y6'i͍!9C5漈uFL<:pӭvPŮ0PUf^:̙ r5+ `-a4{Ks=#nR[$RĒ4Wnj߭GrrEZv~Op9{IBBl$;IG (%#W3!jP}f-*8)3KT[p5e\zbuD@#TY|o]'yl8-þZ's"IN'j -gjIP*kH2~6!ܴ%V W0lP5eiu:m&T_ƘܖmYƂϐodN1jRcHu\̬8Mu ^3|[-A{@yK,˜ ] ֡(u=_ ?6 ēR@|W_s]tJZQcč6,? aBd~??b+jN!=hFz it[*2si3fglR|SLaLkj,eIwL4 ñԬ~Ȧ*tAX GCVvݻƛ7|H_i5(~ܰPh D-#G*܌b] 6,-}mH%mnqUSx'X1aCTlhlhw։$9,@ZnðĩrmWFMјRp+G9:DR:xJ/hX iRwXb,۟l ,]מiCy6^ΞGcKſ*UOFo7mN,? )fc|C=-l$&[*J=J=N EbӐS*_ 5ꄓN{4hG?JPohK-'3˸ϐ!C6ߧIfx.ǢP1 X[,(XZe)By\ Un땽i,?4_wDІ>(suQ.-|þM "%;5Roc V 0MS:MV}`GYnԸ~\zDMlƲCo[<:ޖ;a*܉X(rѩ7|;SC!pq2Y' ,ȗ!jYPH[("K\ U[NO[~;bz0Wh^M{weph. gdTSF66-JPpIr-W\b6LS.AP%d ڵaNὥ[4V.= H͞S-U5dB={i!$P-p!q=T_n |uEpr\mѪ+M:m:&j8ݲa 6DGr@dl=i7:qmƱЦ ?od6$EnCXM' $Ĵ g@u'6$piu9ފ8b[躱bmgvbwEDiIw~C&:F~ L@VCIhŵZlA`ruYɓ̛7CrpiF^d ~:_ʈU2Lզ<1U5sQS)P~b]DSоa73B5l9A@5,7d.uY\rzi</-,inT#6,g4w [ Nd?)+yj'Dl!\bGԸrx,\UpR~H yc(WB~-IQ~\Wp8b/z7q4Ԛ* '5!IH@і,Wv`V(%!%dz'fHT"˜s_-9+ٽ~SNUd(PH 7e8D@]{p*sE! DpuեdNC]a!+2 w!s@#q h`ޫI=*eicJY#P%4=x&(={;ɭ$D z?\KdzRhOX 񅒕FJZ'R9C@/ Vr:Up5AaH3Da"0;pz+?im^|eu8)PCU~U J00|:Ts<^ jM%bhQeR HӋ'fcyNn%N2f/nrEJ4*-ܞY'Xs68ilcȻkI'Raz5E? 0 BcB] {" êZ?K^N%PBԘnZ M*(j"%%ٵG 5h`N /Mb:d5A!ql_$r `s4؋%= iV3bfq䖕yXv TP.B\mԡ\en|N0ڸq erEb!=#4WY[wt]pu&6VtŻc|~+@חՑAJFu< W)?rA$Րа,_AJrCڗxkbؼܔߎ]ح -յ*ffw@;B&0_l]q Jv&ċC݉ G|x]oEfCҁ#es? )@K]ZQv.WhI4 f]J9ϐ3 .X1Lqu *(멕Y e4kЎS-Wإyщz™QX q\FG#E:OPGs,=p)MQ_3Ntu~$Y,߬lP%WDB @ 4Fa5WLUJSwee@ui:\1LETAiˏM'AOTC婨oUnлva)7yH#ٲ 4]ijdȑ-:h%+ 6yX+;n2q.e}h&MS,bjA.O {Oh8Rz&q k4G1 , E'9I g#eVh8bLpm48x4#}4髲%8 }A3$ پ0 ϔd(Ja6}caBT ='#åof@cyj{CXZi)lX #t" T~-FP۳+Q?vd &i."U^ ֔mw DBI/ٗYW̩Pf"))]>K嶏mzm*O:0[Uk.Sܫ-'[uway|ꓴ nK1źQ(Dg1̥c._y^0>6 :bOtTȼOKP?;fdҝs ٷM\H!P g:30nXveaٕɺ:evNԻYpI*r_E'$v3i-N:+:FWWp;RrBDa- mQіRc/}iRlcxu)^:Lz!:?[#A2;*\$crRsJ"[PtUsUҜ![p<.7[x@<7iWamr|R&:J3(is%-A>cg%ܛg->I̝mN[\3cS[6uUۑj^Xcpo)$+QwlO@Fn9tӂyd7(̾Gr4H|Cn~*t/SKZzs}ÐLU;c+5)3-ٵD֣jfcζ4CҦK0e} >W#VQM r$ү%O]Koo9_vRX /+\Ra=G̫*afG1pJ%O*h7:d۸Hg~P||puCYm5'X\=.d)EhDeaapvb IENDB`sinatra-2.0.8.1/lib/sinatra/indifferent_hash.rb000066400000000000000000000114351360317524000213620ustar00rootroot00000000000000# frozen_string_literal: true $stderr.puts <:foo and "foo" are # considered to be the same. # # rgb = Sinatra::IndifferentHash.new # # rgb[:black] = '#000000' # symbol assignment # rgb[:black] # => '#000000' # symbol retrieval # rgb['black'] # => '#000000' # string retrieval # # rgb['white'] = '#FFFFFF' # string assignment # rgb[:white] # => '#FFFFFF' # symbol retrieval # rgb['white'] # => '#FFFFFF' # string retrieval # # Internally, symbols are mapped to strings when used as keys in the entire # writing interface (calling e.g. []=, merge). This mapping # belongs to the public interface. For example, given: # # hash = Sinatra::IndifferentHash.new(:a=>1) # # You are guaranteed that the key is returned as a string: # # hash.keys # => ["a"] # # Technically other types of keys are accepted: # # hash = Sinatra::IndifferentHash.new(:a=>1) # hash[0] = 0 # hash # => { "a"=>1, 0=>0 } # # But this class is intended for use cases where strings or symbols are the # expected keys and it is convenient to understand both as the same. For # example the +params+ hash in Sinatra. class IndifferentHash < Hash def self.[](*args) new.merge!(Hash[*args]) end def initialize(*args) args.map!(&method(:convert_value)) super(*args) end def default(*args) args.map!(&method(:convert_key)) super(*args) end def default=(value) super(convert_value(value)) end def assoc(key) super(convert_key(key)) end def rassoc(value) super(convert_value(value)) end def fetch(key, *args) args.map!(&method(:convert_value)) super(convert_key(key), *args) end def [](key) super(convert_key(key)) end def []=(key, value) super(convert_key(key), convert_value(value)) end alias_method :store, :[]= def key(value) super(convert_value(value)) end def key?(key) super(convert_key(key)) end alias_method :has_key?, :key? alias_method :include?, :key? alias_method :member?, :key? def value?(value) super(convert_value(value)) end alias_method :has_value?, :value? def delete(key) super(convert_key(key)) end def dig(key, *other_keys) super(convert_key(key), *other_keys) end if method_defined?(:dig) # Added in Ruby 2.3 def fetch_values(*keys) keys.map!(&method(:convert_key)) super(*keys) end if method_defined?(:fetch_values) # Added in Ruby 2.3 def slice(*keys) keys.map!(&method(:convert_key)) self.class[super(*keys)] end if method_defined?(:slice) # Added in Ruby 2.5 def values_at(*keys) keys.map!(&method(:convert_key)) super(*keys) end def merge!(*other_hashes) other_hashes.each do |other_hash| if other_hash.is_a?(self.class) super(other_hash) else other_hash.each_pair do |key, value| key = convert_key(key) value = yield(key, self[key], value) if block_given? && key?(key) self[key] = convert_value(value) end end end self end alias_method :update, :merge! def merge(*other_hashes, &block) dup.merge!(*other_hashes, &block) end def replace(other_hash) super(other_hash.is_a?(self.class) ? other_hash : self.class[other_hash]) end if method_defined?(:transform_values!) # Added in Ruby 2.4 def transform_values(&block) dup.transform_values!(&block) end def transform_values! super super(&method(:convert_value)) end end if method_defined?(:transform_keys!) # Added in Ruby 2.5 def transform_keys(&block) dup.transform_keys!(&block) end def transform_keys! super super(&method(:convert_key)) end end private def convert_key(key) key.is_a?(Symbol) ? key.to_s : key end def convert_value(value) case value when Hash value.is_a?(self.class) ? value : self.class[value] when Array value.map(&method(:convert_value)) else value end end end end sinatra-2.0.8.1/lib/sinatra/main.rb000066400000000000000000000033651360317524000170110ustar00rootroot00000000000000module Sinatra ParamsConfig = {} if ARGV.any? require 'optparse' parser = OptionParser.new { |op| op.on('-p port', 'set the port (default is 4567)') { |val| ParamsConfig[:port] = Integer(val) } op.on('-s server', 'specify rack server/handler (default is thin)') { |val| ParamsConfig[:server] = val } op.on('-q', 'turn on quiet mode (default is off)') { ParamsConfig[:quiet] = true } op.on('-x', 'turn on the mutex lock (default is off)') { ParamsConfig[:lock] = true } op.on('-e env', 'set the environment (default is development)') do |val| ENV['RACK_ENV'] = val ParamsConfig[:environment] = val.to_sym end op.on('-o addr', "set the host (default is (env == 'development' ? 'localhost' : '0.0.0.0'))") do |val| ParamsConfig[:bind] = val end } begin parser.parse!(ARGV.dup) rescue => evar ParamsConfig[:optparse_error] = evar end end require 'sinatra/base' class Application < Base # we assume that the first file that requires 'sinatra' is the # app_file. all other path related options are calculated based # on this path by default. set :app_file, caller_files.first || $0 set :run, Proc.new { File.expand_path($0) == File.expand_path(app_file) } if run? && ARGV.any? error = ParamsConfig.delete(:optparse_error) raise error if error ParamsConfig.each { |k, v| set k, v } end end remove_const(:ParamsConfig) at_exit { Application.run! if $!.nil? && Application.run? } end # include would include the module in Object # extend only extends the `main` object extend Sinatra::Delegator class Rack::Builder include Sinatra::Delegator end sinatra-2.0.8.1/lib/sinatra/show_exceptions.rb000066400000000000000000000323101360317524000212760ustar00rootroot00000000000000# frozen_string_literal: true require 'rack/show_exceptions' module Sinatra # Sinatra::ShowExceptions catches all exceptions raised from the app it # wraps. It shows a useful backtrace with the sourcefile and clickable # context, the whole Rack environment and the request data. # # Be careful when you use this on public-facing sites as it could reveal # information helpful to attackers. class ShowExceptions < Rack::ShowExceptions @@eats_errors = Object.new def @@eats_errors.flush(*) end def @@eats_errors.puts(*) end def initialize(app) @app = app end def call(env) @app.call(env) rescue Exception => e errors, env["rack.errors"] = env["rack.errors"], @@eats_errors if prefers_plain_text?(env) content_type = "text/plain" body = dump_exception(e) else content_type = "text/html" body = pretty(env, e) end env["rack.errors"] = errors [ 500, { "Content-Type" => content_type, "Content-Length" => body.bytesize.to_s }, [body] ] end # Pulled from Rack::ShowExceptions in order to override TEMPLATE. # If Rack provides another way to override, this could be removed # in the future. def pretty(env, exception) req = Rack::Request.new(env) # This double assignment is to prevent an "unused variable" warning on # Ruby 1.9.3. Yes, it is dumb, but I don't like Ruby yelling at me. path = path = (req.script_name + req.path_info).squeeze("/") # This double assignment is to prevent an "unused variable" warning on # Ruby 1.9.3. Yes, it is dumb, but I don't like Ruby yelling at me. frames = frames = exception.backtrace.map { |line| frame = OpenStruct.new if line =~ /(.*?):(\d+)(:in `(.*)')?/ frame.filename = $1 frame.lineno = $2.to_i frame.function = $4 begin lineno = frame.lineno-1 lines = ::File.readlines(frame.filename) frame.pre_context_lineno = [lineno-CONTEXT, 0].max frame.pre_context = lines[frame.pre_context_lineno...lineno] frame.context_line = lines[lineno].chomp frame.post_context_lineno = [lineno+CONTEXT, lines.size].min frame.post_context = lines[lineno+1..frame.post_context_lineno] rescue end frame else nil end }.compact TEMPLATE.result(binding) end private def bad_request?(e) Sinatra::BadRequest === e end def prefers_plain_text?(env) !(Request.new(env).preferred_type("text/plain","text/html") == "text/html") && [/curl/].index { |item| item =~ env["HTTP_USER_AGENT"] } end def frame_class(frame) if frame.filename =~ %r{lib/sinatra.*\.rb} "framework" elsif (defined?(Gem) && frame.filename.include?(Gem.dir)) || frame.filename =~ %r{/bin/(\w+)\z} "system" else "app" end end TEMPLATE = ERB.new <<-HTML # :nodoc: <%=h exception.class %> at <%=h path %>

BACKTRACE

(expand)

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

GET

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

No GET data.

<% end %>

POST

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

No POST data.

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

No cookie data.

<% end %>

Rack ENV

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

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

HTML end end sinatra-2.0.8.1/lib/sinatra/version.rb000066400000000000000000000000511360317524000175370ustar00rootroot00000000000000module Sinatra VERSION = '2.0.8.1' end sinatra-2.0.8.1/rack-protection/000077500000000000000000000000001360317524000164265ustar00rootroot00000000000000sinatra-2.0.8.1/rack-protection/.gitignore000066400000000000000000000001741360317524000204200ustar00rootroot00000000000000# please add general patterns to your global ignore list # see https://github.com/github/gitignore#readme Gemfile.lock doc/ sinatra-2.0.8.1/rack-protection/.rspec000066400000000000000000000000511360317524000175370ustar00rootroot00000000000000--color --warnings --require spec_helper sinatra-2.0.8.1/rack-protection/Gemfile000066400000000000000000000004441360317524000177230ustar00rootroot00000000000000source "https://rubygems.org" # encoding: utf-8 gem 'rake' rack_version = ENV['rack'].to_s rack_version = nil if rack_version.empty? or rack_version == 'stable' rack_version = {:github => 'rack/rack'} if rack_version == 'master' gem 'rack', rack_version gem 'sinatra', path: '..' gemspec sinatra-2.0.8.1/rack-protection/License000066400000000000000000000021461360317524000177360ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2011-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/rack-protection/README.md000066400000000000000000000065641360317524000177200ustar00rootroot00000000000000# Rack::Protection This gem protects against typical web attacks. Should work for all Rack apps, including Rails. # Usage Use all protections you probably want to use: ``` ruby # config.ru require 'rack/protection' use Rack::Protection run MyApp ``` Skip a single protection middleware: ``` ruby # config.ru require 'rack/protection' use Rack::Protection, :except => :path_traversal run MyApp ``` Use a single protection middleware: ``` ruby # config.ru require 'rack/protection' use Rack::Protection::AuthenticityToken run MyApp ``` # Prevented Attacks ## Cross Site Request Forgery Prevented by: * [`Rack::Protection::AuthenticityToken`][authenticity-token] (not included by `use Rack::Protection`) * [`Rack::Protection::FormToken`][form-token] (not included by `use Rack::Protection`) * [`Rack::Protection::JsonCsrf`][json-csrf] * [`Rack::Protection::RemoteReferrer`][remote-referrer] (not included by `use Rack::Protection`) * [`Rack::Protection::RemoteToken`][remote-token] * [`Rack::Protection::HttpOrigin`][http-origin] ## Cross Site Scripting Prevented by: * [`Rack::Protection::EscapedParams`][escaped-params] (not included by `use Rack::Protection`) * [`Rack::Protection::XSSHeader`][xss-header] (Internet Explorer and Chrome only) * [`Rack::Protection::ContentSecurityPolicy`][content-security-policy] ## Clickjacking Prevented by: * [`Rack::Protection::FrameOptions`][frame-options] ## Directory Traversal Prevented by: * [`Rack::Protection::PathTraversal`][path-traversal] ## Session Hijacking Prevented by: * [`Rack::Protection::SessionHijacking`][session-hijacking] ## Cookie Tossing Prevented by: * [`Rack::Protection::CookieTossing`][cookie-tossing] (not included by `use Rack::Protection`) ## IP Spoofing Prevented by: * [`Rack::Protection::IPSpoofing`][ip-spoofing] ## Helps to protect against protocol downgrade attacks and cookie hijacking Prevented by: * [`Rack::Protection::StrictTransport`][strict-transport] (not included by `use Rack::Protection`) # Installation gem install rack-protection # Instrumentation Instrumentation is enabled by passing in an instrumenter as an option. ``` use Rack::Protection, instrumenter: ActiveSupport::Notifications ``` The instrumenter is passed a namespace (String) and environment (Hash). The namespace is 'rack.protection' and the attack type can be obtained from the environment key 'rack.protection.attack'. [authenticity-token]: http://www.sinatrarb.com/protection/authenticity_token [content-security-policy]: http://www.sinatrarb.com/protection/content_security_policy [cookie-tossing]: http://www.sinatrarb.com/protection/cookie_tossing [escaped-params]: http://www.sinatrarb.com/protection/escaped_params [form-token]: http://www.sinatrarb.com/protection/form_token [frame-options]: http://www.sinatrarb.com/protection/frame_options [http-origin]: http://www.sinatrarb.com/protection/http_origin [ip-spoofing]: http://www.sinatrarb.com/protection/ip_spoofing [json-csrf]: http://www.sinatrarb.com/protection/json_csrf [path-traversal]: http://www.sinatrarb.com/protection/path_traversal [remote-referrer]: http://www.sinatrarb.com/protection/remote_referrer [remote-token]: http://www.sinatrarb.com/protection/remote_token [session-hijacking]: http://www.sinatrarb.com/protection/session_hijacking [strict-transport]: http://www.sinatrarb.com/protection/strict_transport [xss-header]: http://www.sinatrarb.com/protection/xss_header sinatra-2.0.8.1/rack-protection/Rakefile000066400000000000000000000040421360317524000200730ustar00rootroot00000000000000# encoding: utf-8 $LOAD_PATH.unshift File.expand_path('../lib', __FILE__) begin require 'bundler' Bundler::GemHelper.install_tasks rescue LoadError => e $stderr.puts e end desc "run specs" task(:spec) { ruby '-S rspec spec' } namespace :doc do task :readmes do Dir.glob 'lib/rack/protection/*.rb' do |file| excluded_files = %w[lib/rack/protection/base.rb lib/rack/protection/version.rb] next if excluded_files.include?(file) doc = File.read(file)[/^ module Protection(\n)+( #[^\n]*\n)*/m].scan(/^ *#(?!#) ?(.*)\n/).join("\n") file = "doc/#{file[4..-4].tr("/_", "-")}.rdoc" Dir.mkdir "doc" unless File.directory? "doc" puts "writing #{file}" File.open(file, "w") { |f| f << doc } end end task :index do doc = File.read("README.md") file = "doc/rack-protection-readme.md" Dir.mkdir "doc" unless File.directory? "doc" puts "writing #{file}" File.open(file, "w") { |f| f << doc } end task :all => [:readmes, :index] end desc "generate documentation" task :doc => 'doc:all' desc "generate gemspec" task 'rack-protection.gemspec' do require 'rack/protection/version' content = File.binread 'rack-protection.gemspec' # fetch data fields = { :authors => `git shortlog -sn`.force_encoding('utf-8').scan(/[^\d\s].*/), :email => ["mail@zzak.io", "konstantin.haase@gmail.com"], :files => %w(License README.md Rakefile Gemfile rack-protection.gemspec) + Dir['lib/**/*'] } # insert data fields.each do |field, values| updated = " s.#{field} = [" updated << values.map { |v| "\n %p" % v }.join(',') updated << "\n ]" content.sub!(/ s\.#{field} = \[\n( .*\n)* \]/, updated) end # set version content.sub! /(s\.version.*=\s+).*/, "\\1\"#{Rack::Protection::VERSION}\"" # escape unicode content.gsub!(/./) { |c| c.bytesize > 1 ? "\\u{#{c.codepoints.first.to_s(16)}}" : c } File.open('rack-protection.gemspec', 'w') { |f| f << content } end task :gemspec => 'rack-protection.gemspec' task :default => :spec task :test => :spec sinatra-2.0.8.1/rack-protection/lib/000077500000000000000000000000001360317524000171745ustar00rootroot00000000000000sinatra-2.0.8.1/rack-protection/lib/rack-protection.rb000066400000000000000000000000321360317524000226200ustar00rootroot00000000000000require "rack/protection" sinatra-2.0.8.1/rack-protection/lib/rack/000077500000000000000000000000001360317524000201145ustar00rootroot00000000000000sinatra-2.0.8.1/rack-protection/lib/rack/protection.rb000066400000000000000000000057731360317524000226430ustar00rootroot00000000000000require 'rack/protection/version' require 'rack' module Rack module Protection autoload :AuthenticityToken, 'rack/protection/authenticity_token' autoload :Base, 'rack/protection/base' autoload :CookieTossing, 'rack/protection/cookie_tossing' autoload :ContentSecurityPolicy, 'rack/protection/content_security_policy' autoload :EscapedParams, 'rack/protection/escaped_params' autoload :FormToken, 'rack/protection/form_token' autoload :FrameOptions, 'rack/protection/frame_options' autoload :HttpOrigin, 'rack/protection/http_origin' autoload :IPSpoofing, 'rack/protection/ip_spoofing' autoload :JsonCsrf, 'rack/protection/json_csrf' autoload :PathTraversal, 'rack/protection/path_traversal' autoload :RemoteReferrer, 'rack/protection/remote_referrer' autoload :RemoteToken, 'rack/protection/remote_token' autoload :SessionHijacking, 'rack/protection/session_hijacking' autoload :StrictTransport, 'rack/protection/strict_transport' autoload :XSSHeader, 'rack/protection/xss_header' def self.new(app, options = {}) # does not include: RemoteReferrer, AuthenticityToken and FormToken except = Array options[:except] use_these = Array options[:use] if options.fetch(:without_session, false) except += [:session_hijacking, :remote_token] end Rack::Builder.new do # Off by default, unless added use ::Rack::Protection::AuthenticityToken, options if use_these.include? :authenticity_token use ::Rack::Protection::CookieTossing, options if use_these.include? :cookie_tossing use ::Rack::Protection::ContentSecurityPolicy, options if use_these.include? :content_security_policy use ::Rack::Protection::FormToken, options if use_these.include? :form_token use ::Rack::Protection::RemoteReferrer, options if use_these.include? :remote_referrer use ::Rack::Protection::StrictTransport, options if use_these.include? :strict_transport # On by default, unless skipped use ::Rack::Protection::FrameOptions, options unless except.include? :frame_options use ::Rack::Protection::HttpOrigin, options unless except.include? :http_origin use ::Rack::Protection::IPSpoofing, options unless except.include? :ip_spoofing use ::Rack::Protection::JsonCsrf, options unless except.include? :json_csrf use ::Rack::Protection::PathTraversal, options unless except.include? :path_traversal use ::Rack::Protection::RemoteToken, options unless except.include? :remote_token use ::Rack::Protection::SessionHijacking, options unless except.include? :session_hijacking use ::Rack::Protection::XSSHeader, options unless except.include? :xss_header run app end.to_app end end end sinatra-2.0.8.1/rack-protection/lib/rack/protection/000077500000000000000000000000001360317524000223025ustar00rootroot00000000000000sinatra-2.0.8.1/rack-protection/lib/rack/protection/authenticity_token.rb000066400000000000000000000137561360317524000265550ustar00rootroot00000000000000require 'rack/protection' require 'securerandom' require 'base64' module Rack module Protection ## # Prevented attack:: CSRF # Supported browsers:: all # More infos:: http://en.wikipedia.org/wiki/Cross-site_request_forgery # # This middleware only accepts requests other than GET, # HEAD, OPTIONS, TRACE if their given access # token matches the token included in the session. # # It checks the X-CSRF-Token header and the POST form # data. # # Compatible with the {rack-csrf}[https://rubygems.org/gems/rack_csrf] gem. # # == Options # # [:authenticity_param] the name of the param that should contain # the token on a request. Default value: # "authenticity_token" # # == Example: Forms application # # To show what the AuthenticityToken does, this section includes a sample # program which shows two forms. One with, and one without a CSRF token # The one without CSRF token field will get a 403 Forbidden response. # # Install the gem, then run the program: # # gem install 'rack-protection' # ruby server.rb # # Here is server.rb: # # require 'rack/protection' # # app = Rack::Builder.app do # use Rack::Session::Cookie, secret: 'secret' # use Rack::Protection::AuthenticityToken # # run -> (env) do # [200, {}, [ # <<~EOS # # # # # rack-protection minimal example # # #

Without Authenticity Token

#

This takes you to Forbidden

#
# # #
# #

With Authenticity Token

#

This successfully takes you to back to this form.

#
# # # #
# # # EOS # ]] # end # end # # Rack::Handler::WEBrick.run app # # == Example: Customize which POST parameter holds the token # # To customize the authenticity parameter for form data, use the # :authenticity_param option: # use Rack::Protection::AuthenticityToken, authenticity_param: 'your_token_param_name' class AuthenticityToken < Base TOKEN_LENGTH = 32 default_options :authenticity_param => 'authenticity_token', :allow_if => nil def self.token(session) self.new(nil).mask_authenticity_token(session) end def self.random_token SecureRandom.base64(TOKEN_LENGTH) end def accepts?(env) session = session env set_token(session) safe?(env) || valid_token?(session, env['HTTP_X_CSRF_TOKEN']) || valid_token?(session, Request.new(env).params[options[:authenticity_param]]) || ( options[:allow_if] && options[:allow_if].call(env) ) end def mask_authenticity_token(session) token = set_token(session) mask_token(token) end private def set_token(session) session[:csrf] ||= self.class.random_token end # Checks the client's masked token to see if it matches the # session token. def valid_token?(session, token) return false if token.nil? || token.empty? begin token = decode_token(token) rescue ArgumentError # encoded_masked_token is invalid Base64 return false end # See if it's actually a masked token or not. We should be able # to handle any unmasked tokens that we've issued without error. if unmasked_token?(token) compare_with_real_token token, session elsif masked_token?(token) token = unmask_token(token) compare_with_real_token token, session else false # Token is malformed end end # Creates a masked version of the authenticity token that varies # on each request. The masking is used to mitigate SSL attacks # like BREACH. def mask_token(token) token = decode_token(token) one_time_pad = SecureRandom.random_bytes(token.length) encrypted_token = xor_byte_strings(one_time_pad, token) masked_token = one_time_pad + encrypted_token encode_token(masked_token) end # Essentially the inverse of +mask_token+. def unmask_token(masked_token) # Split the token into the one-time pad and the encrypted # value and decrypt it token_length = masked_token.length / 2 one_time_pad = masked_token[0...token_length] encrypted_token = masked_token[token_length..-1] xor_byte_strings(one_time_pad, encrypted_token) end def unmasked_token?(token) token.length == TOKEN_LENGTH end def masked_token?(token) token.length == TOKEN_LENGTH * 2 end def compare_with_real_token(token, session) secure_compare(token, real_token(session)) end def real_token(session) decode_token(session[:csrf]) end def encode_token(token) Base64.strict_encode64(token) end def decode_token(token) Base64.strict_decode64(token) end def xor_byte_strings(s1, s2) s1.bytes.zip(s2.bytes).map { |(c1,c2)| c1 ^ c2 }.pack('c*') end end end end sinatra-2.0.8.1/rack-protection/lib/rack/protection/base.rb000066400000000000000000000065531360317524000235520ustar00rootroot00000000000000require 'rack/protection' require 'rack/utils' require 'digest' require 'logger' require 'uri' module Rack module Protection class Base DEFAULT_OPTIONS = { :reaction => :default_reaction, :logging => true, :message => 'Forbidden', :encryptor => Digest::SHA1, :session_key => 'rack.session', :status => 403, :allow_empty_referrer => true, :report_key => "protection.failed", :html_types => %w[text/html application/xhtml text/xml application/xml] } attr_reader :app, :options def self.default_options(options) define_method(:default_options) { super().merge(options) } end def self.default_reaction(reaction) alias_method(:default_reaction, reaction) end def default_options DEFAULT_OPTIONS end def initialize(app, options = {}) @app, @options = app, default_options.merge(options) end def safe?(env) %w[GET HEAD OPTIONS TRACE].include? env['REQUEST_METHOD'] end def accepts?(env) raise NotImplementedError, "#{self.class} implementation pending" end def call(env) unless accepts? env instrument env result = react env end result or app.call(env) end def react(env) result = send(options[:reaction], env) result if Array === result and result.size == 3 end def warn(env, message) return unless options[:logging] l = options[:logger] || env['rack.logger'] || ::Logger.new(env['rack.errors']) l.warn(message) end def instrument(env) return unless i = options[:instrumenter] env['rack.protection.attack'] = self.class.name.split('::').last.downcase i.instrument('rack.protection', env) end def deny(env) warn env, "attack prevented by #{self.class}" [options[:status], {'Content-Type' => 'text/plain'}, [options[:message]]] end def report(env) warn env, "attack reported by #{self.class}" env[options[:report_key]] = true end def session?(env) env.include? options[:session_key] end def session(env) return env[options[:session_key]] if session? env fail "you need to set up a session middleware *before* #{self.class}" end def drop_session(env) session(env).clear if session? env end def referrer(env) ref = env['HTTP_REFERER'].to_s return if !options[:allow_empty_referrer] and ref.empty? URI.parse(ref).host || Request.new(env).host rescue URI::InvalidURIError end def origin(env) env['HTTP_ORIGIN'] || env['HTTP_X_ORIGIN'] end def random_string(secure = defined? SecureRandom) secure ? SecureRandom.hex(16) : "%032x" % rand(2**128-1) rescue NotImplementedError random_string false end def encrypt(value) options[:encryptor].hexdigest value.to_s end def secure_compare(a, b) Rack::Utils.secure_compare(a.to_s, b.to_s) end alias default_reaction deny def html?(headers) return false unless header = headers.detect { |k,v| k.downcase == 'content-type' } options[:html_types].include? header.last[/^\w+\/\w+/] end end end end sinatra-2.0.8.1/rack-protection/lib/rack/protection/content_security_policy.rb000066400000000000000000000065751360317524000276240ustar00rootroot00000000000000# -*- coding: utf-8 -*- require 'rack/protection' module Rack module Protection ## # Prevented attack:: XSS and others # Supported browsers:: Firefox 23+, Safari 7+, Chrome 25+, Opera 15+ # # Description:: Content Security Policy, a mechanism web applications # can use to mitigate a broad class of content injection # vulnerabilities, such as cross-site scripting (XSS). # Content Security Policy is a declarative policy that lets # the authors (or server administrators) of a web application # inform the client about the sources from which the # application expects to load resources. # # More info:: W3C CSP Level 1 : https://www.w3.org/TR/CSP1/ (deprecated) # W3C CSP Level 2 : https://www.w3.org/TR/CSP2/ (current) # W3C CSP Level 3 : https://www.w3.org/TR/CSP3/ (draft) # https://developer.mozilla.org/en-US/docs/Web/Security/CSP # http://caniuse.com/#search=ContentSecurityPolicy # http://content-security-policy.com/ # https://securityheaders.io # https://scotthelme.co.uk/csp-cheat-sheet/ # http://www.html5rocks.com/en/tutorials/security/content-security-policy/ # # Sets the 'Content-Security-Policy[-Report-Only]' header. # # Options: ContentSecurityPolicy configuration is a complex topic with # several levels of support that has evolved over time. # See the W3C documentation and the links in the more info # section for CSP usage examples and best practices. The # CSP3 directives in the 'NO_ARG_DIRECTIVES' constant need to be # presented in the options hash with a boolean 'true' in order # to be used in a policy. # class ContentSecurityPolicy < Base default_options default_src: :none, script_src: "'self'", img_src: "'self'", style_src: "'self'", connect_src: "'self'", report_only: false DIRECTIVES = %i(base_uri child_src connect_src default_src font_src form_action frame_ancestors frame_src img_src manifest_src media_src object_src plugin_types referrer reflected_xss report_to report_uri require_sri_for sandbox script_src style_src worker_src).freeze NO_ARG_DIRECTIVES = %i(block_all_mixed_content disown_opener upgrade_insecure_requests).freeze def csp_policy directives = [] DIRECTIVES.each do |d| if options.key?(d) directives << "#{d.to_s.sub(/_/, '-')} #{options[d]}" end end # Set these key values to boolean 'true' to include in policy NO_ARG_DIRECTIVES.each do |d| if options.key?(d) && options[d].is_a?(TrueClass) directives << d.to_s.sub(/_/, '-') end end directives.compact.sort.join('; ') end def call(env) status, headers, body = @app.call(env) header = options[:report_only] ? 'Content-Security-Policy-Report-Only' : 'Content-Security-Policy' headers[header] ||= csp_policy if html? headers [status, headers, body] end end end end sinatra-2.0.8.1/rack-protection/lib/rack/protection/cookie_tossing.rb000066400000000000000000000041731360317524000256530ustar00rootroot00000000000000require 'rack/protection' require 'pathname' module Rack module Protection ## # Prevented attack:: Cookie Tossing # Supported browsers:: all # More infos:: https://github.com/blog/1466-yummy-cookies-across-domains # # Does not accept HTTP requests if the HTTP_COOKIE header contains more than one # session cookie. This does not protect against a cookie overflow attack. # # Options: # # session_key:: The name of the session cookie (default: 'rack.session') class CookieTossing < Base default_reaction :deny def call(env) status, headers, body = super response = Rack::Response.new(body, status, headers) request = Rack::Request.new(env) remove_bad_cookies(request, response) response.finish end def accepts?(env) cookie_header = env['HTTP_COOKIE'] cookies = Rack::Utils.parse_query(cookie_header, ';,') { |s| s } cookies.each do |k, v| if k == session_key && Array(v).size > 1 bad_cookies << k elsif k != session_key && Rack::Utils.unescape(k) == session_key bad_cookies << k end end bad_cookies.empty? end def remove_bad_cookies(request, response) return if bad_cookies.empty? paths = cookie_paths(request.path) bad_cookies.each do |name| paths.each { |path| response.set_cookie name, empty_cookie(request.host, path) } end end def redirect(env) request = Request.new(env) warn env, "attack prevented by #{self.class}" [302, {'Content-Type' => 'text/html', 'Location' => request.path}, []] end def bad_cookies @bad_cookies ||= [] end def cookie_paths(path) path = '/' if path.to_s.empty? paths = [] Pathname.new(path).descend { |p| paths << p.to_s } paths end def empty_cookie(host, path) {:value => '', :domain => host, :path => path, :expires => Time.at(0)} end def session_key @session_key ||= options[:session_key] end end end end sinatra-2.0.8.1/rack-protection/lib/rack/protection/escaped_params.rb000066400000000000000000000045071360317524000256040ustar00rootroot00000000000000require 'rack/protection' require 'rack/utils' require 'tempfile' begin require 'escape_utils' rescue LoadError end module Rack module Protection ## # Prevented attack:: XSS # Supported browsers:: all # More infos:: http://en.wikipedia.org/wiki/Cross-site_scripting # # Automatically escapes Rack::Request#params so they can be embedded in HTML # or JavaScript without any further issues. Calls +html_safe+ on the escaped # strings if defined, to avoid double-escaping in Rails. # # Options: # escape:: What escaping modes to use, should be Symbol or Array of Symbols. # Available: :html (default), :javascript, :url class EscapedParams < Base extend Rack::Utils class << self alias escape_url escape public :escape_html end default_options :escape => :html, :escaper => defined?(EscapeUtils) ? EscapeUtils : self def initialize(*) super modes = Array options[:escape] @escaper = options[:escaper] @html = modes.include? :html @javascript = modes.include? :javascript @url = modes.include? :url if @javascript and not @escaper.respond_to? :escape_javascript fail("Use EscapeUtils for JavaScript escaping.") end end def call(env) request = Request.new(env) get_was = handle(request.GET) post_was = handle(request.POST) rescue nil app.call env ensure request.GET.replace get_was if get_was request.POST.replace post_was if post_was end def handle(hash) was = hash.dup hash.replace escape(hash) was end def escape(object) case object when Hash then escape_hash(object) when Array then object.map { |o| escape(o) } when String then escape_string(object) when Tempfile then object else nil end end def escape_hash(hash) hash = hash.dup hash.each { |k,v| hash[k] = escape(v) } hash end def escape_string(str) str = @escaper.escape_url(str) if @url str = @escaper.escape_html(str) if @html str = @escaper.escape_javascript(str) if @javascript str end end end end sinatra-2.0.8.1/rack-protection/lib/rack/protection/form_token.rb000066400000000000000000000013271360317524000247750ustar00rootroot00000000000000require 'rack/protection' module Rack module Protection ## # Prevented attack:: CSRF # Supported browsers:: all # More infos:: http://en.wikipedia.org/wiki/Cross-site_request_forgery # # Only accepts submitted forms if a given access token matches the token # included in the session. Does not expect such a token from Ajax request. # # This middleware is not used when using the Rack::Protection collection, # since it might be a security issue, depending on your application # # Compatible with rack-csrf. class FormToken < AuthenticityToken def accepts?(env) env["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest" or super end end end end sinatra-2.0.8.1/rack-protection/lib/rack/protection/frame_options.rb000066400000000000000000000023441360317524000254770ustar00rootroot00000000000000require 'rack/protection' module Rack module Protection ## # Prevented attack:: Clickjacking # Supported browsers:: Internet Explorer 8, Firefox 3.6.9, Opera 10.50, # Safari 4.0, Chrome 4.1.249.1042 and later # More infos:: https://developer.mozilla.org/en/The_X-FRAME-OPTIONS_response_header # # Sets X-Frame-Options header to tell the browser avoid embedding the page # in a frame. # # Options: # # frame_options:: Defines who should be allowed to embed the page in a # frame. Use :deny to forbid any embedding, :sameorigin # to allow embedding from the same origin (default). class FrameOptions < Base default_options :frame_options => :sameorigin def frame_options @frame_options ||= begin frame_options = options[:frame_options] frame_options = options[:frame_options].to_s.upcase unless frame_options.respond_to? :to_str frame_options.to_str end end def call(env) status, headers, body = @app.call(env) headers['X-Frame-Options'] ||= frame_options if html? headers [status, headers, body] end end end end sinatra-2.0.8.1/rack-protection/lib/rack/protection/http_origin.rb000066400000000000000000000026741360317524000251660ustar00rootroot00000000000000require 'rack/protection' module Rack module Protection ## # Prevented attack:: CSRF # Supported browsers:: Google Chrome 2, Safari 4 and later # More infos:: http://en.wikipedia.org/wiki/Cross-site_request_forgery # http://tools.ietf.org/html/draft-abarth-origin # # Does not accept unsafe HTTP requests when value of Origin HTTP request header # does not match default or whitelisted URIs. # # If you want to whitelist a specific domain, you can pass in as the `:origin_whitelist` option: # # use Rack::Protection, origin_whitelist: ["http://localhost:3000", "http://127.0.01:3000"] # # The `:allow_if` option can also be set to a proc to use custom allow/deny logic. class HttpOrigin < Base DEFAULT_PORTS = { 'http' => 80, 'https' => 443, 'coffee' => 80 } default_reaction :deny default_options :allow_if => nil def base_url(env) request = Rack::Request.new(env) port = ":#{request.port}" unless request.port == DEFAULT_PORTS[request.scheme] "#{request.scheme}://#{request.host}#{port}" end def accepts?(env) return true if safe? env return true unless origin = env['HTTP_ORIGIN'] return true if base_url(env) == origin return true if options[:allow_if] && options[:allow_if].call(env) Array(options[:origin_whitelist]).include? origin end end end end sinatra-2.0.8.1/rack-protection/lib/rack/protection/ip_spoofing.rb000066400000000000000000000013011360317524000251360ustar00rootroot00000000000000require 'rack/protection' module Rack module Protection ## # Prevented attack:: IP spoofing # Supported browsers:: all # More infos:: http://blog.c22.cc/2011/04/22/surveymonkey-ip-spoofing/ # # Detect (some) IP spoofing attacks. class IPSpoofing < Base default_reaction :deny def accepts?(env) return true unless env.include? 'HTTP_X_FORWARDED_FOR' ips = env['HTTP_X_FORWARDED_FOR'].split(/\s*,\s*/) return false if env.include? 'HTTP_CLIENT_IP' and not ips.include? env['HTTP_CLIENT_IP'] return false if env.include? 'HTTP_X_REAL_IP' and not ips.include? env['HTTP_X_REAL_IP'] true end end end end sinatra-2.0.8.1/rack-protection/lib/rack/protection/json_csrf.rb000066400000000000000000000034271360317524000246230ustar00rootroot00000000000000require 'rack/protection' module Rack module Protection ## # Prevented attack:: CSRF # Supported browsers:: all # More infos:: http://flask.pocoo.org/docs/0.10/security/#json-security # http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx # # JSON GET APIs are vulnerable to being embedded as JavaScript when the # Array prototype has been patched to track data. Checks the referrer # even on GET requests if the content type is JSON. # # If request includes Origin HTTP header, defers to HttpOrigin to determine # if the request is safe. Please refer to the documentation for more info. # # The `:allow_if` option can be set to a proc to use custom allow/deny logic. class JsonCsrf < Base default_options :allow_if => nil alias react deny def call(env) request = Request.new(env) status, headers, body = app.call(env) if has_vector?(request, headers) warn env, "attack prevented by #{self.class}" react_and_close(env, body) or [status, headers, body] else [status, headers, body] end end def has_vector?(request, headers) return false if request.xhr? return false if options[:allow_if] && options[:allow_if].call(request.env) return false unless headers['Content-Type'].to_s.split(';', 2).first =~ /^\s*application\/json\s*$/ origin(request.env).nil? and referrer(request.env) != request.host end def react_and_close(env, body) reaction = react(env) close_body(body) if reaction reaction end def close_body(body) body.close if body.respond_to?(:close) end end end end sinatra-2.0.8.1/rack-protection/lib/rack/protection/path_traversal.rb000066400000000000000000000023401360317524000256450ustar00rootroot00000000000000require 'rack/protection' module Rack module Protection ## # Prevented attack:: Directory traversal # Supported browsers:: all # More infos:: http://en.wikipedia.org/wiki/Directory_traversal # # Unescapes '/' and '.', expands +path_info+. # Thus GET /foo/%2e%2e%2fbar becomes GET /bar. class PathTraversal < Base def call(env) path_was = env["PATH_INFO"] env["PATH_INFO"] = cleanup path_was if path_was && !path_was.empty? app.call env ensure env["PATH_INFO"] = path_was end def cleanup(path) encoding = path.encoding dot = '.'.encode(encoding) slash = '/'.encode(encoding) backslash = '\\'.encode(encoding) parts = [] unescaped = path.gsub(/%2e/i, dot).gsub(/%2f/i, slash).gsub(/%5c/i, backslash) unescaped = unescaped.gsub(backslash, slash) unescaped.split(slash).each do |part| next if part.empty? or part == dot part == '..' ? parts.pop : parts << part end cleaned = slash + parts.join(slash) cleaned << slash if parts.any? and unescaped =~ %r{/\.{0,2}$} cleaned end end end end sinatra-2.0.8.1/rack-protection/lib/rack/protection/remote_referrer.rb000066400000000000000000000007621360317524000260230ustar00rootroot00000000000000require 'rack/protection' module Rack module Protection ## # Prevented attack:: CSRF # Supported browsers:: all # More infos:: http://en.wikipedia.org/wiki/Cross-site_request_forgery # # Does not accept unsafe HTTP requests if the Referer [sic] header is set to # a different host. class RemoteReferrer < Base default_reaction :deny def accepts?(env) safe?(env) or referrer(env) == Request.new(env).host end end end end sinatra-2.0.8.1/rack-protection/lib/rack/protection/remote_token.rb000066400000000000000000000011221360317524000253160ustar00rootroot00000000000000require 'rack/protection' module Rack module Protection ## # Prevented attack:: CSRF # Supported browsers:: all # More infos:: http://en.wikipedia.org/wiki/Cross-site_request_forgery # # Only accepts unsafe HTTP requests if a given access token matches the token # included in the session *or* the request comes from the same origin. # # Compatible with rack-csrf. class RemoteToken < AuthenticityToken default_reaction :deny def accepts?(env) super or referrer(env) == Request.new(env).host end end end end sinatra-2.0.8.1/rack-protection/lib/rack/protection/session_hijacking.rb000066400000000000000000000022371360317524000263250ustar00rootroot00000000000000require 'rack/protection' module Rack module Protection ## # Prevented attack:: Session Hijacking # Supported browsers:: all # More infos:: http://en.wikipedia.org/wiki/Session_hijacking # # Tracks request properties like the user agent in the session and empties # the session if those properties change. This essentially prevents attacks # from Firesheep. Since all headers taken into consideration can be # spoofed, too, this will not prevent determined hijacking attempts. class SessionHijacking < Base default_reaction :drop_session default_options :tracking_key => :tracking, :encrypt_tracking => true, :track => %w[HTTP_USER_AGENT] def accepts?(env) session = session env key = options[:tracking_key] if session.include? key session[key].all? { |k,v| v == encrypt(env[k]) } else session[key] = {} options[:track].each { |k| session[key][k] = encrypt(env[k]) } end end def encrypt(value) value = value.to_s.downcase options[:encrypt_tracking] ? super(value) : value end end end end sinatra-2.0.8.1/rack-protection/lib/rack/protection/strict_transport.rb000066400000000000000000000027151360317524000262600ustar00rootroot00000000000000require 'rack/protection' module Rack module Protection ## # Prevented attack:: Protects against against protocol downgrade attacks and cookie hijacking. # Supported browsers:: all # More infos:: https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security # # browser will prevent any communications from being sent over HTTP # to the specified domain and will instead send all communications over HTTPS. # It also prevents HTTPS click through prompts on browsers. # # Options: # # max_age:: How long future requests to the domain should go over HTTPS; specified in seconds # include_subdomains:: If all present and future subdomains will be HTTPS # preload:: Allow this domain to be included in browsers HSTS preload list. See https://hstspreload.appspot.com/ class StrictTransport < Base default_options :max_age => 31_536_000, :include_subdomains => false, :preload => false def strict_transport @strict_transport ||= begin strict_transport = 'max-age=' + options[:max_age].to_s strict_transport += '; includeSubDomains' if options[:include_subdomains] strict_transport += '; preload' if options[:preload] strict_transport.to_str end end def call(env) status, headers, body = @app.call(env) headers['Strict-Transport-Security'] ||= strict_transport [status, headers, body] end end end end sinatra-2.0.8.1/rack-protection/lib/rack/protection/version.rb000066400000000000000000000001021360317524000243050ustar00rootroot00000000000000module Rack module Protection VERSION = '2.0.8.1' end end sinatra-2.0.8.1/rack-protection/lib/rack/protection/xss_header.rb000066400000000000000000000015461360317524000247620ustar00rootroot00000000000000require 'rack/protection' module Rack module Protection ## # Prevented attack:: Non-permanent XSS # Supported browsers:: Internet Explorer 8+ and Chrome # More infos:: http://blogs.msdn.com/b/ie/archive/2008/07/01/ie8-security-part-iv-the-xss-filter.aspx # # Sets X-XSS-Protection header to tell the browser to block attacks. # # Options: # xss_mode:: How the browser should prevent the attack (default: :block) class XSSHeader < Base default_options :xss_mode => :block, :nosniff => true def call(env) status, headers, body = @app.call(env) headers['X-XSS-Protection'] ||= "1; mode=#{options[:xss_mode]}" if html? headers headers['X-Content-Type-Options'] ||= 'nosniff' if options[:nosniff] [status, headers, body] end end end end sinatra-2.0.8.1/rack-protection/rack-protection.gemspec000066400000000000000000000024401360317524000230770ustar00rootroot00000000000000version = File.read(File.expand_path("../../VERSION", __FILE__)).strip Gem::Specification.new do |s| # general infos s.name = "rack-protection" s.version = version s.description = "Protect against typical web attacks, works with all Rack apps, including Rails." s.homepage = "http://sinatrarb.com/protection/" s.summary = s.description s.license = 'MIT' s.authors = ["https://github.com/sinatra/sinatra/graphs/contributors"] s.email = "sinatrarb@googlegroups.com" s.files = Dir["lib/**/*.rb"] + [ "License", "README.md", "Rakefile", "Gemfile", "rack-protection.gemspec" ] if s.respond_to?(:metadata) s.metadata = { 'source_code_uri' => 'https://github.com/sinatra/sinatra/tree/master/rack-protection', 'homepage_uri' => 'http://sinatrarb.com/protection/', 'documentation_uri' => 'https://www.rubydoc.info/gems/rack-protection' } else raise <<-EOF RubyGems 2.0 or newer is required to protect against public gem pushes. You can update your rubygems version by running: gem install rubygems-update update_rubygems: gem update --system EOF end # dependencies s.add_dependency "rack" s.add_development_dependency "rack-test" s.add_development_dependency "rspec", "~> 3.6" end sinatra-2.0.8.1/rack-protection/spec/000077500000000000000000000000001360317524000173605ustar00rootroot00000000000000sinatra-2.0.8.1/rack-protection/spec/lib/000077500000000000000000000000001360317524000201265ustar00rootroot00000000000000sinatra-2.0.8.1/rack-protection/spec/lib/rack/000077500000000000000000000000001360317524000210465ustar00rootroot00000000000000sinatra-2.0.8.1/rack-protection/spec/lib/rack/protection/000077500000000000000000000000001360317524000232345ustar00rootroot00000000000000sinatra-2.0.8.1/rack-protection/spec/lib/rack/protection/authenticity_token_spec.rb000066400000000000000000000056051360317524000305130ustar00rootroot00000000000000describe Rack::Protection::AuthenticityToken do let(:token) { described_class.random_token } let(:masked_token) { described_class.token(session) } let(:bad_token) { Base64.strict_encode64("badtoken") } let(:session) { {:csrf => token} } it_behaves_like "any rack application" it "denies post requests without any token" do expect(post('/')).not_to be_ok end it "accepts post requests with correct X-CSRF-Token header" do post('/', {}, 'rack.session' => session, 'HTTP_X_CSRF_TOKEN' => token) expect(last_response).to be_ok end it "accepts post requests with masked X-CSRF-Token header" do post('/', {}, 'rack.session' => session, 'HTTP_X_CSRF_TOKEN' => masked_token) expect(last_response).to be_ok end it "denies post requests with wrong X-CSRF-Token header" do post('/', {}, 'rack.session' => session, 'HTTP_X_CSRF_TOKEN' => bad_token) expect(last_response).not_to be_ok end it "accepts post form requests with correct authenticity_token field" do post('/', {"authenticity_token" => token}, 'rack.session' => session) expect(last_response).to be_ok end it "accepts post form requests with masked authenticity_token field" do post('/', {"authenticity_token" => masked_token}, 'rack.session' => session) expect(last_response).to be_ok end it "denies post form requests with wrong authenticity_token field" do post('/', {"authenticity_token" => bad_token}, 'rack.session' => session) expect(last_response).not_to be_ok end it "prevents ajax requests without a valid token" do expect(post('/', {}, "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest")).not_to be_ok end it "allows for a custom authenticity token param" do mock_app do use Rack::Protection::AuthenticityToken, :authenticity_param => 'csrf_param' run proc { |e| [200, {'Content-Type' => 'text/plain'}, ['hi']] } end post('/', {"csrf_param" => token}, 'rack.session' => {:csrf => token}) expect(last_response).to be_ok end it "sets a new csrf token for the session in env, even after a 'safe' request" do get('/', {}, {}) expect(env['rack.session'][:csrf]).not_to be_nil end describe ".token" do it "returns a unique masked version of the authenticity token" do expect(Rack::Protection::AuthenticityToken.token(session)).not_to eq(masked_token) end it "sets a session authenticity token if one does not exist" do session = {} allow(Rack::Protection::AuthenticityToken).to receive(:random_token).and_return(token) allow_any_instance_of(Rack::Protection::AuthenticityToken).to receive(:mask_token).and_return(masked_token) Rack::Protection::AuthenticityToken.token(session) expect(session[:csrf]).to eq(token) end end describe ".random_token" do it "generates a base64 encoded 32 character string" do expect(Base64.strict_decode64(token).length).to eq(32) end end end sinatra-2.0.8.1/rack-protection/spec/lib/rack/protection/base_spec.rb000066400000000000000000000023561360317524000255130ustar00rootroot00000000000000describe Rack::Protection::Base do subject { described_class.new(lambda {}) } describe "#random_string" do it "outputs a string of 32 characters" do expect(subject.random_string.length).to eq(32) end end describe "#referrer" do it "Reads referrer from Referer header" do env = {"HTTP_HOST" => "foo.com", "HTTP_REFERER" => "http://bar.com/valid"} expect(subject.referrer(env)).to eq("bar.com") end it "Reads referrer from Host header when Referer header is relative" do env = {"HTTP_HOST" => "foo.com", "HTTP_REFERER" => "/valid"} expect(subject.referrer(env)).to eq("foo.com") end it "Reads referrer from Host header when Referer header is missing" do env = {"HTTP_HOST" => "foo.com"} expect(subject.referrer(env)).to eq("foo.com") end it "Returns nil when Referer header is missing and allow_empty_referrer is false" do env = {"HTTP_HOST" => "foo.com"} subject.options[:allow_empty_referrer] = false expect(subject.referrer(env)).to be_nil end it "Returns nil when Referer header is invalid" do env = {"HTTP_HOST" => "foo.com", "HTTP_REFERER" => "http://bar.com/bad|uri"} expect(subject.referrer(env)).to be_nil end end end sinatra-2.0.8.1/rack-protection/spec/lib/rack/protection/content_security_policy_spec.rb000066400000000000000000000067171360317524000315660ustar00rootroot00000000000000describe Rack::Protection::ContentSecurityPolicy do it_behaves_like "any rack application" it 'should set the Content Security Policy' do expect( get('/', {}, 'wants' => 'text/html').headers["Content-Security-Policy"] ).to eq("connect-src 'self'; default-src none; img-src 'self'; script-src 'self'; style-src 'self'") end it 'should not set the Content Security Policy for other content types' do headers = get('/', {}, 'wants' => 'text/foo').headers expect(headers["Content-Security-Policy"]).to be_nil expect(headers["Content-Security-Policy-Report-Only"]).to be_nil end it 'should allow changing the protection settings' do mock_app do use Rack::Protection::ContentSecurityPolicy, :default_src => 'none', :script_src => 'https://cdn.mybank.net', :style_src => 'https://cdn.mybank.net', :img_src => 'https://cdn.mybank.net', :connect_src => 'https://api.mybank.com', :frame_src => 'self', :font_src => 'https://cdn.mybank.net', :object_src => 'https://cdn.mybank.net', :media_src => 'https://cdn.mybank.net', :report_uri => '/my_amazing_csp_report_parser', :sandbox => 'allow-scripts' run DummyApp end headers = get('/', {}, 'wants' => 'text/html').headers expect(headers["Content-Security-Policy"]).to eq("connect-src https://api.mybank.com; default-src none; font-src https://cdn.mybank.net; frame-src self; img-src https://cdn.mybank.net; media-src https://cdn.mybank.net; object-src https://cdn.mybank.net; report-uri /my_amazing_csp_report_parser; sandbox allow-scripts; script-src https://cdn.mybank.net; style-src https://cdn.mybank.net") expect(headers["Content-Security-Policy-Report-Only"]).to be_nil end it 'should allow setting CSP3 no arg directives' do mock_app do use Rack::Protection::ContentSecurityPolicy, :block_all_mixed_content => true, :disown_opener => true, :upgrade_insecure_requests => true run DummyApp end headers = get('/', {}, 'wants' => 'text/html').headers expect(headers["Content-Security-Policy"]).to eq("block-all_mixed_content; connect-src 'self'; default-src none; disown-opener; img-src 'self'; script-src 'self'; style-src 'self'; upgrade-insecure_requests") end it 'should ignore CSP3 no arg directives unless they are set to true' do mock_app do use Rack::Protection::ContentSecurityPolicy, :block_all_mixed_content => false, :disown_opener => 'false', :upgrade_insecure_requests => 'foo' run DummyApp end headers = get('/', {}, 'wants' => 'text/html').headers expect(headers["Content-Security-Policy"]).to eq("connect-src 'self'; default-src none; img-src 'self'; script-src 'self'; style-src 'self'") end it 'should allow changing report only' do # I have no clue what other modes are available mock_app do use Rack::Protection::ContentSecurityPolicy, :report_uri => '/my_amazing_csp_report_parser', :report_only => true run DummyApp end headers = get('/', {}, 'wants' => 'text/html').headers expect(headers["Content-Security-Policy"]).to be_nil expect(headers["Content-Security-Policy-Report-Only"]).to eq("connect-src 'self'; default-src none; img-src 'self'; report-uri /my_amazing_csp_report_parser; script-src 'self'; style-src 'self'") end it 'should not override the header if already set' do mock_app with_headers("Content-Security-Policy" => "default-src: none") expect(get('/', {}, 'wants' => 'text/html').headers["Content-Security-Policy"]).to eq("default-src: none") end end sinatra-2.0.8.1/rack-protection/spec/lib/rack/protection/cookie_tossing_spec.rb000066400000000000000000000053421360317524000276160ustar00rootroot00000000000000describe Rack::Protection::CookieTossing do it_behaves_like "any rack application" context 'with default reaction' do before(:each) do mock_app do use Rack::Protection::CookieTossing run DummyApp end end it 'accepts requests with a single session cookie' do get '/', {}, 'HTTP_COOKIE' => 'rack.session=SESSION_TOKEN' expect(last_response).to be_ok end it 'denies requests with duplicate session cookies' do get '/', {}, 'HTTP_COOKIE' => 'rack.session=EVIL_SESSION_TOKEN; rack.session=SESSION_TOKEN' expect(last_response).not_to be_ok end it 'denies requests with sneaky encoded session cookies' do get '/', {}, 'HTTP_COOKIE' => 'rack.session=EVIL_SESSION_TOKEN; rack.%73ession=SESSION_TOKEN' expect(last_response).not_to be_ok end it 'adds the correct Set-Cookie header' do get '/some/path', {}, 'HTTP_COOKIE' => 'rack.%73ession=EVIL_SESSION_TOKEN; rack.session=EVIL_SESSION_TOKEN; rack.session=SESSION_TOKEN' expected_header = <<-END.chomp rack.%2573ession=; domain=example.org; path=/; expires=Thu, 01 Jan 1970 00:00:00 -0000 rack.%2573ession=; domain=example.org; path=/some; expires=Thu, 01 Jan 1970 00:00:00 -0000 rack.%2573ession=; domain=example.org; path=/some/path; expires=Thu, 01 Jan 1970 00:00:00 -0000 rack.session=; domain=example.org; path=/; expires=Thu, 01 Jan 1970 00:00:00 -0000 rack.session=; domain=example.org; path=/some; expires=Thu, 01 Jan 1970 00:00:00 -0000 rack.session=; domain=example.org; path=/some/path; expires=Thu, 01 Jan 1970 00:00:00 -0000 END expect(last_response.headers['Set-Cookie']).to eq(expected_header) end end context 'with redirect reaction' do before(:each) do mock_app do use Rack::Protection::CookieTossing, :reaction => :redirect run DummyApp end end it 'redirects requests with duplicate session cookies' do get '/', {}, 'HTTP_COOKIE' => 'rack.session=EVIL_SESSION_TOKEN; rack.session=SESSION_TOKEN' expect(last_response).to be_redirect expect(last_response.location).to eq('/') end it 'redirects requests with sneaky encoded session cookies' do get '/path', {}, 'HTTP_COOKIE' => 'rack.%73ession=EVIL_SESSION_TOKEN; rack.session=SESSION_TOKEN' expect(last_response).to be_redirect expect(last_response.location).to eq('/path') end end context 'with custom session key' do it 'denies requests with duplicate session cookies' do mock_app do use Rack::Protection::CookieTossing, :session_key => '_session' run DummyApp end get '/', {}, 'HTTP_COOKIE' => '_session=EVIL_SESSION_TOKEN; _session=SESSION_TOKEN' expect(last_response).not_to be_ok end end end sinatra-2.0.8.1/rack-protection/spec/lib/rack/protection/escaped_params_spec.rb000066400000000000000000000035701360317524000275470ustar00rootroot00000000000000describe Rack::Protection::EscapedParams do it_behaves_like "any rack application" context 'escaping' do it 'escapes html entities' do mock_app do |env| request = Rack::Request.new(env) [200, {'Content-Type' => 'text/plain'}, [request.params['foo']]] end get '/', :foo => "" expect(body).to eq('<bar>') end it 'leaves normal params untouched' do mock_app do |env| request = Rack::Request.new(env) [200, {'Content-Type' => 'text/plain'}, [request.params['foo']]] end get '/', :foo => "bar" expect(body).to eq('bar') end it 'copes with nested arrays' do mock_app do |env| request = Rack::Request.new(env) [200, {'Content-Type' => 'text/plain'}, [request.params['foo']['bar']]] end get '/', :foo => {:bar => ""} expect(body).to eq('<bar>') end it 'leaves cache-breaker params untouched' do mock_app do |env| [200, {'Content-Type' => 'text/plain'}, ['hi']] end get '/?95df8d9bf5237ad08df3115ee74dcb10' expect(body).to eq('hi') end it 'leaves TempFiles untouched' do mock_app do |env| request = Rack::Request.new(env) [200, {'Content-Type' => 'text/plain'}, [request.params['file'][:filename] + "\n" + \ request.params['file'][:tempfile].read + "\n" + \ request.params['other']]] end temp_file = File.open('_escaped_params_tmp_file', 'w') begin temp_file.write('hello world') temp_file.close post '/', :file => Rack::Test::UploadedFile.new(temp_file.path), :other => '' expect(body).to eq("_escaped_params_tmp_file\nhello world\n<bar>") ensure File.unlink(temp_file.path) end end end end sinatra-2.0.8.1/rack-protection/spec/lib/rack/protection/form_token_spec.rb000066400000000000000000000031701360317524000267370ustar00rootroot00000000000000describe Rack::Protection::FormToken do let(:token) { described_class.random_token } let(:masked_token) { described_class.token(session) } let(:bad_token) { Base64.strict_encode64("badtoken") } let(:session) { {:csrf => token} } it_behaves_like "any rack application" it "denies post requests without any token" do expect(post('/')).not_to be_ok end it "accepts post requests with correct X-CSRF-Token header" do post('/', {}, 'rack.session' => session, 'HTTP_X_CSRF_TOKEN' => token) expect(last_response).to be_ok end it "accepts post requests with masked X-CSRF-Token header" do post('/', {}, 'rack.session' => session, 'HTTP_X_CSRF_TOKEN' => masked_token) expect(last_response).to be_ok end it "denies post requests with wrong X-CSRF-Token header" do post('/', {}, 'rack.session' => session, 'HTTP_X_CSRF_TOKEN' => bad_token) expect(last_response).not_to be_ok end it "accepts post form requests with correct authenticity_token field" do post('/', {"authenticity_token" => token}, 'rack.session' => session) expect(last_response).to be_ok end it "accepts post form requests with masked authenticity_token field" do post('/', {"authenticity_token" => masked_token}, 'rack.session' => session) expect(last_response).to be_ok end it "denies post form requests with wrong authenticity_token field" do post('/', {"authenticity_token" => bad_token}, 'rack.session' => session) expect(last_response).not_to be_ok end it "accepts ajax requests without a valid token" do expect(post('/', {}, "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest")).to be_ok end end sinatra-2.0.8.1/rack-protection/spec/lib/rack/protection/frame_options_spec.rb000066400000000000000000000023531360317524000274430ustar00rootroot00000000000000describe Rack::Protection::FrameOptions do it_behaves_like "any rack application" it 'should set the X-Frame-Options' do expect(get('/', {}, 'wants' => 'text/html').headers["X-Frame-Options"]).to eq("SAMEORIGIN") end it 'should not set the X-Frame-Options for other content types' do expect(get('/', {}, 'wants' => 'text/foo').headers["X-Frame-Options"]).to be_nil end it 'should allow changing the protection mode' do # I have no clue what other modes are available mock_app do use Rack::Protection::FrameOptions, :frame_options => :deny run DummyApp end expect(get('/', {}, 'wants' => 'text/html').headers["X-Frame-Options"]).to eq("DENY") end it 'should allow changing the protection mode to a string' do # I have no clue what other modes are available mock_app do use Rack::Protection::FrameOptions, :frame_options => "ALLOW-FROM foo" run DummyApp end expect(get('/', {}, 'wants' => 'text/html').headers["X-Frame-Options"]).to eq("ALLOW-FROM foo") end it 'should not override the header if already set' do mock_app with_headers("X-Frame-Options" => "allow") expect(get('/', {}, 'wants' => 'text/html').headers["X-Frame-Options"]).to eq("allow") end end sinatra-2.0.8.1/rack-protection/spec/lib/rack/protection/http_origin_spec.rb000066400000000000000000000026661360317524000271330ustar00rootroot00000000000000describe Rack::Protection::HttpOrigin do it_behaves_like "any rack application" before(:each) do mock_app do use Rack::Protection::HttpOrigin run DummyApp end end %w(GET HEAD POST PUT DELETE).each do |method| it "accepts #{method} requests with no Origin" do expect(send(method.downcase, '/')).to be_ok end end %w(GET HEAD).each do |method| it "accepts #{method} requests with non-whitelisted Origin" do expect(send(method.downcase, '/', {}, 'HTTP_ORIGIN' => 'http://malicious.com')).to be_ok end end %w(GET HEAD POST PUT DELETE).each do |method| it "accepts #{method} requests when allow_if is true" do mock_app do use Rack::Protection::HttpOrigin, :allow_if => lambda{|env| env.has_key?('HTTP_ORIGIN') } run DummyApp end expect(send(method.downcase, '/', {}, 'HTTP_ORIGIN' => 'http://any.domain.com')).to be_ok end end %w(POST PUT DELETE).each do |method| it "denies #{method} requests with non-whitelisted Origin" do expect(send(method.downcase, '/', {}, 'HTTP_ORIGIN' => 'http://malicious.com')).not_to be_ok end it "accepts #{method} requests with whitelisted Origin" do mock_app do use Rack::Protection::HttpOrigin, :origin_whitelist => ['http://www.friend.com'] run DummyApp end expect(send(method.downcase, '/', {}, 'HTTP_ORIGIN' => 'http://www.friend.com')).to be_ok end end end sinatra-2.0.8.1/rack-protection/spec/lib/rack/protection/ip_spoofing_spec.rb000066400000000000000000000022671360317524000271160ustar00rootroot00000000000000describe Rack::Protection::IPSpoofing do it_behaves_like "any rack application" it 'accepts requests without X-Forward-For header' do get('/', {}, 'HTTP_CLIENT_IP' => '1.2.3.4', 'HTTP_X_REAL_IP' => '4.3.2.1') expect(last_response).to be_ok end it 'accepts requests with proper X-Forward-For header' do get('/', {}, 'HTTP_CLIENT_IP' => '1.2.3.4', 'HTTP_X_FORWARDED_FOR' => '192.168.1.20, 1.2.3.4, 127.0.0.1') expect(last_response).to be_ok end it 'denies requests where the client spoofs X-Forward-For but not the IP' do get('/', {}, 'HTTP_CLIENT_IP' => '1.2.3.4', 'HTTP_X_FORWARDED_FOR' => '1.2.3.5') expect(last_response).not_to be_ok end it 'denies requests where the client spoofs the IP but not X-Forward-For' do get('/', {}, 'HTTP_CLIENT_IP' => '1.2.3.5', 'HTTP_X_FORWARDED_FOR' => '192.168.1.20, 1.2.3.4, 127.0.0.1') expect(last_response).not_to be_ok end it 'denies requests where IP and X-Forward-For are spoofed but not X-Real-IP' do get('/', {}, 'HTTP_CLIENT_IP' => '1.2.3.5', 'HTTP_X_FORWARDED_FOR' => '1.2.3.5', 'HTTP_X_REAL_IP' => '1.2.3.4') expect(last_response).not_to be_ok end end sinatra-2.0.8.1/rack-protection/spec/lib/rack/protection/json_csrf_spec.rb000066400000000000000000000055661360317524000265750ustar00rootroot00000000000000describe Rack::Protection::JsonCsrf do it_behaves_like "any rack application" module DummyAppWithBody module Closeable def close @closed = true end def closed? @closed end end def self.body @body ||= begin body = ['ok'] body.extend(Closeable) body end end def self.call(env) Thread.current[:last_env] = env [200, {'Content-Type' => 'application/json'}, body] end end describe 'json response' do before do mock_app { |e| [200, {'Content-Type' => 'application/json'}, []]} end it "denies get requests with json responses with a remote referrer" do expect(get('/', {}, 'HTTP_REFERER' => 'http://evil.com')).not_to be_ok end it "closes the body returned by the app if it denies the get request" do mock_app DummyAppWithBody do |e| [200, {'Content-Type' => 'application/json'}, []] end get('/', {}, 'HTTP_REFERER' => 'http://evil.com') expect(DummyAppWithBody.body).to be_closed end it "accepts requests with json responses with a remote referrer when allow_if is true" do mock_app do use Rack::Protection::JsonCsrf, :allow_if => lambda{|env| env['HTTP_REFERER'] == 'http://good.com'} run proc { |e| [200, {'Content-Type' => 'application/json'}, []]} end expect(get('/', {}, 'HTTP_REFERER' => 'http://good.com')).to be_ok end it "accepts requests with json responses with a remote referrer when there's an origin header set" do expect(get('/', {}, 'HTTP_REFERER' => 'http://good.com', 'HTTP_ORIGIN' => 'http://good.com')).to be_ok end it "accepts requests with json responses with a remote referrer when there's an x-origin header set" do expect(get('/', {}, 'HTTP_REFERER' => 'http://good.com', 'HTTP_X_ORIGIN' => 'http://good.com')).to be_ok end it "accepts get requests with json responses with a local referrer" do expect(get('/', {}, 'HTTP_REFERER' => '/')).to be_ok end it "accepts get requests with json responses with no referrer" do expect(get('/', {})).to be_ok end it "accepts XHR requests" do expect(get('/', {}, 'HTTP_REFERER' => 'http://evil.com', 'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest')).to be_ok end end describe 'not json response' do it "accepts get requests with 304 headers" do mock_app { |e| [304, {}, []]} expect(get('/', {}).status).to eq(304) end end describe 'with drop_session as default reaction' do it 'still denies' do mock_app do use Rack::Protection, :reaction => :drop_session run proc { |e| [200, {'Content-Type' => 'application/json'}, []]} end session = {:foo => :bar} get('/', {}, 'HTTP_REFERER' => 'http://evil.com', 'rack.session' => session) expect(last_response).not_to be_ok end end end sinatra-2.0.8.1/rack-protection/spec/lib/rack/protection/path_traversal_spec.rb000066400000000000000000000024031360317524000276110ustar00rootroot00000000000000describe Rack::Protection::PathTraversal do it_behaves_like "any rack application" context 'escaping' do before do mock_app { |e| [200, {'Content-Type' => 'text/plain'}, [e['PATH_INFO']]] } end %w[/foo/bar /foo/bar/ / /.f /a.x].each do |path| it("does not touch #{path.inspect}") { expect(get(path).body).to eq(path) } end { # yes, this is ugly, feel free to change that '/..' => '/', '/a/../b' => '/b', '/a/../b/' => '/b/', '/a/.' => '/a/', '/%2e.' => '/', '/a/%2E%2e/b' => '/b', '/a%2f%2E%2e%2Fb/' => '/b/', '//' => '/', '/%2fetc%2Fpasswd' => '/etc/passwd' }.each do |a, b| it("replaces #{a.inspect} with #{b.inspect}") { expect(get(a).body).to eq(b) } end it 'should be able to deal with PATH_INFO = nil (fcgi?)' do app = Rack::Protection::PathTraversal.new(proc { 42 }) expect(app.call({})).to eq(42) end end context "PATH_INFO's encoding" do before do @app = Rack::Protection::PathTraversal.new(proc { |e| [200, {'Content-Type' => 'text/plain'}, [e['PATH_INFO'].encoding.to_s]] }) end it 'should remain unchanged as ASCII-8BIT' do body = @app.call({ 'PATH_INFO' => '/'.encode('ASCII-8BIT') })[2][0] expect(body).to eq('ASCII-8BIT') end end end sinatra-2.0.8.1/rack-protection/spec/lib/rack/protection/protection_spec.rb000066400000000000000000000106661360317524000267720ustar00rootroot00000000000000describe Rack::Protection do it_behaves_like "any rack application" it 'passes on options' do mock_app do use Rack::Protection, :track => ['HTTP_FOO'] run proc { |e| [200, {'Content-Type' => 'text/plain'}, ['hi']] } end session = {:foo => :bar} get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_ENCODING' => 'a' get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_ENCODING' => 'b' expect(session[:foo]).to eq(:bar) get '/', {}, 'rack.session' => session, 'HTTP_FOO' => 'BAR' expect(session).to be_empty end it 'passes errors through if :reaction => :report is used' do mock_app do use Rack::Protection, :reaction => :report run proc { |e| [200, {'Content-Type' => 'text/plain'}, [e["protection.failed"].to_s]] } end session = {:foo => :bar} post('/', {}, 'rack.session' => session, 'HTTP_ORIGIN' => 'http://malicious.com') expect(last_response).to be_ok expect(body).to eq("true") end describe "#react" do it 'prevents attacks and warns about it' do io = StringIO.new mock_app do use Rack::Protection, :logger => Logger.new(io) run DummyApp end post('/', {}, 'rack.session' => {}, 'HTTP_ORIGIN' => 'http://malicious.com') expect(io.string).to match(/prevented.*Origin/) end it 'reports attacks if reaction is to report' do io = StringIO.new mock_app do use Rack::Protection, :reaction => :report, :logger => Logger.new(io) run DummyApp end post('/', {}, 'rack.session' => {}, 'HTTP_ORIGIN' => 'http://malicious.com') expect(io.string).to match(/reported.*Origin/) expect(io.string).not_to match(/prevented.*Origin/) end it 'passes errors to reaction method if specified' do io = StringIO.new Rack::Protection::Base.send(:define_method, :special) { |*args| io << args.inspect } mock_app do use Rack::Protection, :reaction => :special, :logger => Logger.new(io) run DummyApp end post('/', {}, 'rack.session' => {}, 'HTTP_ORIGIN' => 'http://malicious.com') expect(io.string).to match(/HTTP_ORIGIN.*malicious.com/) expect(io.string).not_to match(/reported|prevented/) end end describe "#html?" do context "given an appropriate content-type header" do subject { Rack::Protection::Base.new(nil).html? 'content-type' => "text/html" } it { is_expected.to be_truthy } end context "given an appropriate content-type header of text/xml" do subject { Rack::Protection::Base.new(nil).html? 'content-type' => "text/xml" } it { is_expected.to be_truthy } end context "given an appropriate content-type header of application/xml" do subject { Rack::Protection::Base.new(nil).html? 'content-type' => "application/xml" } it { is_expected.to be_truthy } end context "given an inappropriate content-type header" do subject { Rack::Protection::Base.new(nil).html? 'content-type' => "image/gif" } it { is_expected.to be_falsey } end context "given no content-type header" do subject { Rack::Protection::Base.new(nil).html?({}) } it { is_expected.to be_falsey } end end describe "#instrument" do let(:env) { { 'rack.protection.attack' => 'base' } } let(:instrumenter) { double('Instrumenter') } after do app.instrument(env) end context 'with an instrumenter specified' do let(:app) { Rack::Protection::Base.new(nil, :instrumenter => instrumenter) } it { expect(instrumenter).to receive(:instrument).with('rack.protection', env) } end context 'with no instrumenter specified' do let(:app) { Rack::Protection::Base.new(nil) } it { expect(instrumenter).not_to receive(:instrument) } end end describe "new" do it 'should allow disable session protection' do mock_app do use Rack::Protection, :without_session => true run DummyApp end session = {:foo => :bar} get '/', {}, 'rack.session' => session, 'HTTP_USER_AGENT' => 'a' get '/', {}, 'rack.session' => session, 'HTTP_USER_AGENT' => 'b' expect(session[:foo]).to eq :bar end it 'should allow disable CSRF protection' do mock_app do use Rack::Protection, :without_session => true run DummyApp end post('/', {}, 'HTTP_REFERER' => 'http://example.com/foo', 'HTTP_HOST' => 'example.org') expect(last_response).to be_ok end end end sinatra-2.0.8.1/rack-protection/spec/lib/rack/protection/remote_referrer_spec.rb000066400000000000000000000016621360317524000277670ustar00rootroot00000000000000describe Rack::Protection::RemoteReferrer do it_behaves_like "any rack application" it "accepts post requests with no referrer" do expect(post('/')).to be_ok end it "does not accept post requests with no referrer if allow_empty_referrer is false" do mock_app do use Rack::Protection::RemoteReferrer, :allow_empty_referrer => false run DummyApp end expect(post('/')).not_to be_ok end it "should allow post request with a relative referrer" do expect(post('/', {}, 'HTTP_REFERER' => '/')).to be_ok end it "accepts post requests with the same host in the referrer" do post('/', {}, 'HTTP_REFERER' => 'http://example.com/foo', 'HTTP_HOST' => 'example.com') expect(last_response).to be_ok end it "denies post requests with a remote referrer" do post('/', {}, 'HTTP_REFERER' => 'http://example.com/foo', 'HTTP_HOST' => 'example.org') expect(last_response).not_to be_ok end end sinatra-2.0.8.1/rack-protection/spec/lib/rack/protection/remote_token_spec.rb000066400000000000000000000046211360317524000272710ustar00rootroot00000000000000describe Rack::Protection::RemoteToken do let(:token) { described_class.random_token } let(:masked_token) { described_class.token(session) } let(:bad_token) { Base64.strict_encode64("badtoken") } let(:session) { {:csrf => token} } it_behaves_like "any rack application" it "accepts post requests with no referrer" do expect(post('/')).to be_ok end it "accepts post requests with a local referrer" do expect(post('/', {}, 'HTTP_REFERER' => '/')).to be_ok end it "denies post requests with a remote referrer and no token" do post('/', {}, 'HTTP_REFERER' => 'http://example.com/foo', 'HTTP_HOST' => 'example.org') expect(last_response).not_to be_ok end it "accepts post requests with a remote referrer and correct X-CSRF-Token header" do post('/', {}, 'HTTP_REFERER' => 'http://example.com/foo', 'HTTP_HOST' => 'example.org', 'rack.session' => session, 'HTTP_X_CSRF_TOKEN' => token) expect(last_response).to be_ok end it "accepts post requests with a remote referrer and masked X-CSRF-Token header" do post('/', {}, 'HTTP_REFERER' => 'http://example.com/foo', 'HTTP_HOST' => 'example.org', 'rack.session' => session, 'HTTP_X_CSRF_TOKEN' => masked_token) expect(last_response).to be_ok end it "denies post requests with a remote referrer and wrong X-CSRF-Token header" do post('/', {}, 'HTTP_REFERER' => 'http://example.com/foo', 'HTTP_HOST' => 'example.org', 'rack.session' => session, 'HTTP_X_CSRF_TOKEN' => bad_token) expect(last_response).not_to be_ok end it "accepts post form requests with a remote referrer and correct authenticity_token field" do post('/', {"authenticity_token" => token}, 'HTTP_REFERER' => 'http://example.com/foo', 'HTTP_HOST' => 'example.org', 'rack.session' => session) expect(last_response).to be_ok end it "accepts post form requests with a remote referrer and masked authenticity_token field" do post('/', {"authenticity_token" => masked_token}, 'HTTP_REFERER' => 'http://example.com/foo', 'HTTP_HOST' => 'example.org', 'rack.session' => session) expect(last_response).to be_ok end it "denies post form requests with a remote referrer and wrong authenticity_token field" do post('/', {"authenticity_token" => bad_token}, 'HTTP_REFERER' => 'http://example.com/foo', 'HTTP_HOST' => 'example.org', 'rack.session' => session) expect(last_response).not_to be_ok end end sinatra-2.0.8.1/rack-protection/spec/lib/rack/protection/session_hijacking_spec.rb000066400000000000000000000022701360317524000302660ustar00rootroot00000000000000describe Rack::Protection::SessionHijacking do it_behaves_like "any rack application" it "accepts a session without changes to tracked parameters" do session = {:foo => :bar} get '/', {}, 'rack.session' => session get '/', {}, 'rack.session' => session expect(session[:foo]).to eq(:bar) end it "denies requests with a changing User-Agent header" do session = {:foo => :bar} get '/', {}, 'rack.session' => session, 'HTTP_USER_AGENT' => 'a' get '/', {}, 'rack.session' => session, 'HTTP_USER_AGENT' => 'b' expect(session).to be_empty end it "accepts requests with a changing Accept-Encoding header" do # this is tested because previously it led to clearing the session session = {:foo => :bar} get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_ENCODING' => 'a' get '/', {}, 'rack.session' => session, 'HTTP_ACCEPT_ENCODING' => 'b' expect(session).not_to be_empty end it "accepts requests with a changing Version header"do session = {:foo => :bar} get '/', {}, 'rack.session' => session, 'HTTP_VERSION' => '1.0' get '/', {}, 'rack.session' => session, 'HTTP_VERSION' => '1.1' expect(session[:foo]).to eq(:bar) end end sinatra-2.0.8.1/rack-protection/spec/lib/rack/protection/strict_transport_spec.rb000066400000000000000000000027261360317524000302260ustar00rootroot00000000000000describe Rack::Protection::StrictTransport do it_behaves_like "any rack application" it 'should set the Strict-Transport-Security header' do expect(get('/', {}, 'wants' => 'text/html').headers["Strict-Transport-Security"]).to eq("max-age=31536000") end it 'should allow changing the max-age option' do mock_app do use Rack::Protection::StrictTransport, :max_age => 16_070_400 run DummyApp end expect(get('/', {}, 'wants' => 'text/html').headers["Strict-Transport-Security"]).to eq("max-age=16070400") end it 'should allow switching on the include_subdomains option' do mock_app do use Rack::Protection::StrictTransport, :include_subdomains => true run DummyApp end expect(get('/', {}, 'wants' => 'text/html').headers["Strict-Transport-Security"]).to eq("max-age=31536000; includeSubDomains") end it 'should allow switching on the preload option' do mock_app do use Rack::Protection::StrictTransport, :preload => true run DummyApp end expect(get('/', {}, 'wants' => 'text/html').headers["Strict-Transport-Security"]).to eq("max-age=31536000; preload") end it 'should allow switching on all the options' do mock_app do use Rack::Protection::StrictTransport, :preload => true, :include_subdomains => true run DummyApp end expect(get('/', {}, 'wants' => 'text/html').headers["Strict-Transport-Security"]).to eq("max-age=31536000; includeSubDomains; preload") end end sinatra-2.0.8.1/rack-protection/spec/lib/rack/protection/xss_header_spec.rb000066400000000000000000000036041360317524000267230ustar00rootroot00000000000000describe Rack::Protection::XSSHeader do it_behaves_like "any rack application" it 'should set the X-XSS-Protection' do expect(get('/', {}, 'wants' => 'text/html;charset=utf-8').headers["X-XSS-Protection"]).to eq("1; mode=block") end it 'should set the X-XSS-Protection for XHTML' do expect(get('/', {}, 'wants' => 'application/xhtml+xml').headers["X-XSS-Protection"]).to eq("1; mode=block") end it 'should not set the X-XSS-Protection for other content types' do expect(get('/', {}, 'wants' => 'application/foo').headers["X-XSS-Protection"]).to be_nil end it 'should allow changing the protection mode' do # I have no clue what other modes are available mock_app do use Rack::Protection::XSSHeader, :xss_mode => :foo run DummyApp end expect(get('/', {}, 'wants' => 'application/xhtml').headers["X-XSS-Protection"]).to eq("1; mode=foo") end it 'should not override the header if already set' do mock_app with_headers("X-XSS-Protection" => "0") expect(get('/', {}, 'wants' => 'text/html').headers["X-XSS-Protection"]).to eq("0") end it 'should set the X-Content-Type-Options' do expect(get('/', {}, 'wants' => 'text/html').header["X-Content-Type-Options"]).to eq("nosniff") end it 'should set the X-Content-Type-Options for other content types' do expect(get('/', {}, 'wants' => 'application/foo').header["X-Content-Type-Options"]).to eq("nosniff") end it 'should allow changing the nosniff-mode off' do mock_app do use Rack::Protection::XSSHeader, :nosniff => false run DummyApp end expect(get('/').headers["X-Content-Type-Options"]).to be_nil end it 'should not override the header if already set X-Content-Type-Options' do mock_app with_headers("X-Content-Type-Options" => "sniff") expect(get('/', {}, 'wants' => 'text/html').headers["X-Content-Type-Options"]).to eq("sniff") end end sinatra-2.0.8.1/rack-protection/spec/spec_helper.rb000066400000000000000000000071661360317524000222100ustar00rootroot00000000000000require 'rack/protection' require 'rack/test' require 'rack' Dir[File.expand_path('../support/**/*.rb', __FILE__)].each { |f| require f } # This file was generated by the `rspec --init` command. Conventionally, all # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. # The generated `.rspec` file contains `--require spec_helper` which will cause this # file to always be loaded, without a need to explicitly require it in any files. # # Given that it is always loaded, you are encouraged to keep this file as # light-weight as possible. Requiring heavyweight dependencies from this file # will add to the boot time of your test suite on EVERY test run, even for an # individual file that may not need all of that loaded. Instead, make a # separate helper file that requires this one and then use it only in the specs # that actually need it. # # The `.rspec` file also contains a few flags that are not defaults but that # users commonly want. # # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration RSpec.configure do |config| # The settings below are suggested to provide a good initial experience # with RSpec, but feel free to customize to your heart's content. # These two settings work together to allow you to limit a spec run # to individual examples or groups you care about by tagging them with # `:focus` metadata. When nothing is tagged with `:focus`, all examples # get run. config.filter_run :focus config.run_all_when_everything_filtered = true # Many RSpec users commonly either run the entire suite or an individual # file, and it's useful to allow more verbose output when running an # individual spec file. if config.files_to_run.one? # Use the documentation formatter for detailed output, # unless a formatter has already been configured # (e.g. via a command-line flag). config.default_formatter = 'doc' end # Print the 10 slowest examples and example groups at the # end of the spec run, to help surface which specs are running # particularly slow. config.profile_examples = 10 # Run specs in random order to surface order dependencies. If you find an # order dependency and want to debug it, you can fix the order by providing # the seed, which is printed after each run. # --seed 1234 config.order = :random # Seed global randomization in this process using the `--seed` CLI option. # Setting this allows you to use `--seed` to deterministically reproduce # test failures related to randomization by passing the same `--seed` value # as the one that triggered the failure. Kernel.srand config.seed # rspec-expectations config goes here. You can use an alternate # assertion/expectation library such as wrong or the stdlib/minitest # assertions if you prefer. config.expect_with :rspec do |expectations| # Enable only the newer, non-monkey-patching expect syntax. # For more details, see: # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax expectations.syntax = :expect end # rspec-mocks config goes here. You can use an alternate test double # library (such as bogus or mocha) by changing the `mock_with` option here. config.mock_with :rspec do |mocks| # Enable only the newer, non-monkey-patching expect syntax. # For more details, see: # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ mocks.syntax = :expect # Prevents you from mocking or stubbing a method that does not exist on # a real object. This is generally recommended. mocks.verify_partial_doubles = true end config.include Rack::Test::Methods config.include SpecHelpers endsinatra-2.0.8.1/rack-protection/spec/support/000077500000000000000000000000001360317524000210745ustar00rootroot00000000000000sinatra-2.0.8.1/rack-protection/spec/support/dummy_app.rb000066400000000000000000000003171360317524000234150ustar00rootroot00000000000000module DummyApp def self.call(env) Thread.current[:last_env] = env body = (env['REQUEST_METHOD'] == 'HEAD' ? '' : 'ok') [200, {'Content-Type' => env['wants'] || 'text/plain'}, [body]] end endsinatra-2.0.8.1/rack-protection/spec/support/not_implemented_as_pending.rb000066400000000000000000000011331360317524000267710ustar00rootroot00000000000000# see http://blog.101ideas.cz/posts/pending-examples-via-not-implemented-error-in-rspec.html module NotImplementedAsPending def self.included(base) base.class_eval do alias_method :__finish__, :finish remove_method :finish end end def finish(reporter) if @exception.is_a?(NotImplementedError) from = @exception.backtrace[0] message = "#{@exception.message} (from #{from})" @pending_declared_in_example = message metadata[:pending] = true @exception = nil end __finish__(reporter) end RSpec::Core::Example.send :include, self endsinatra-2.0.8.1/rack-protection/spec/support/rack_monkey_patches.rb000066400000000000000000000012251360317524000254320ustar00rootroot00000000000000if defined? Gem.loaded_specs and Gem.loaded_specs.include? 'rack' version = Gem.loaded_specs['rack'].version.to_s else version = Rack.release + '.0' end if version == "1.3" Rack::Session::Abstract::ID.class_eval do private def prepare_session(env) session_was = env[ENV_SESSION_KEY] env[ENV_SESSION_KEY] = SessionHash.new(self, env) env[ENV_SESSION_OPTIONS_KEY] = OptionsHash.new(self, env, @default_options) env[ENV_SESSION_KEY].merge! session_was if session_was end end end unless Rack::MockResponse.method_defined? :header Rack::MockResponse.send(:alias_method, :header, :headers) end sinatra-2.0.8.1/rack-protection/spec/support/shared_examples.rb000066400000000000000000000026671360317524000246000ustar00rootroot00000000000000shared_examples_for 'any rack application' do it "should not interfere with normal get requests" do expect(get('/')).to be_ok expect(body).to eq('ok') end it "should not interfere with normal head requests" do expect(head('/')).to be_ok end it 'should not leak changes to env' do klass = described_class detector = Struct.new(:app) do def call(env) was = env.dup res = app.call(env) was.each do |k,v| next if env[k] == v fail "env[#{k.inspect}] changed from #{v.inspect} to #{env[k].inspect}" end res end end mock_app do use Rack::Head use(Rack::Config) { |e| e['rack.session'] ||= {}} use detector use klass run DummyApp end expect(get('/..', :foo => '')).to be_ok end it 'allows passing on values in env' do klass = described_class changer = Struct.new(:app) do def call(env) env['foo.bar'] = 42 app.call(env) end end detector = Struct.new(:app) do def call(env) app.call(env) end end expect_any_instance_of(detector).to receive(:call).with( hash_including('foo.bar' => 42) ).and_call_original mock_app do use Rack::Head use(Rack::Config) { |e| e['rack.session'] ||= {}} use changer use klass use detector run DummyApp end expect(get('/')).to be_ok end end sinatra-2.0.8.1/rack-protection/spec/support/spec_helpers.rb000066400000000000000000000014031360317524000240730ustar00rootroot00000000000000require 'forwardable' module SpecHelpers extend Forwardable def_delegators :last_response, :body, :headers, :status, :errors def_delegators :current_session, :env_for attr_writer :app def app @app ||= nil @app || mock_app(DummyApp) end def mock_app(app = nil, &block) app = block if app.nil? and block.arity == 1 if app klass = described_class mock_app do use Rack::Head use(Rack::Config) { |e| e['rack.session'] ||= {}} use klass run app end else @app = Rack::Lint.new Rack::Builder.new(&block).to_app end end def with_headers(headers) proc { [200, {'Content-Type' => 'text/plain'}.merge(headers), ['ok']] } end def env Thread.current[:last_env] end endsinatra-2.0.8.1/sinatra-contrib/000077500000000000000000000000001360317524000164215ustar00rootroot00000000000000sinatra-2.0.8.1/sinatra-contrib/.gitignore000066400000000000000000000000331360317524000204050ustar00rootroot00000000000000doc/ .bundle/ Gemfile.lock sinatra-2.0.8.1/sinatra-contrib/.travis.yml000066400000000000000000000010621360317524000205310ustar00rootroot00000000000000--- language: ruby before_install: - rvm get head - gem install bundler rvm: - 2.2.4 - 2.3.1 - rbx - jruby-9.1.0.0 - jruby-head - ruby-head matrix: include: - { rvm: 2.3.1, env: tilt=master } - { rvm: ruby-head, env: tilt=master } - { rvm: 2.3.1, env: rack=master } allow_failures: - env: tilt=master - rvm: ruby-head - rvm: jruby-head - rvm: rbx - env: rack=master notifications: recipients: - _@trevorbramble.com - mail@zzak.io slack: sinatrarb:LQGhUfGYcqRgRzwKea0bqUhY sudo: false sinatra-2.0.8.1/sinatra-contrib/Gemfile000066400000000000000000000020071360317524000177130ustar00rootroot00000000000000source "https://rubygems.org" unless ENV['QUICK'] gemspec gem 'sinatra', path: '..' gem 'rack-protection', path: '../rack-protection' gem "twitter-text", "1.14.7" group :development, :test do platform :jruby do gem 'json' gem 'rdoc' end platform :jruby do gem 'therubyrhino' end platform :jruby, :ruby do gem 'hamlit' gem 'hamlit-block', '>= 0.7.1' gem 'liquid', '~> 2.6.x' gem 'slim' end platform :ruby do gem 'execjs', '2.0.0' gem 'nokogiri', '1.5.10' gem 'redcarpet', '2.3.0' gem 'yajl-ruby' # ref is a dependency of therubyracer gem 'ref' gem 'therubyracer' end gem 'multi_json' end # Allows stuff like `tilt=1.2.2 bundle install` or `tilt=master ...`. # Used by the CI. repos = { 'tilt' => 'rtomayko/tilt', 'rack' => 'rack/rack' } %w[tilt rack].each do |lib| dep = (ENV[lib] || 'stable').sub "#{lib}-", '' dep = nil if dep == 'stable' dep = {:github => repos[lib], :branch => dep} if dep and dep !~ /(\d+\.)+\d+/ gem lib, dep if dep end sinatra-2.0.8.1/sinatra-contrib/LICENSE000066400000000000000000000022051360317524000174250ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2008-2017 Nicolas Sanguinetti, entp.com, 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/sinatra-contrib/README.md000066400000000000000000000125101360317524000176770ustar00rootroot00000000000000# Sinatra::Contrib Collection of common Sinatra extensions, semi-officially supported. ## Goals * For every future Sinatra release, have at least one fully compatible release * High code quality, high test coverage * Include plugins people usually ask for a lot ## Included extensions ### Common Extensions These are common extension which will not add significant overhead or change any behavior of already existing APIs. They do not add any dependencies not already installed with this gem. Currently included: * [`sinatra/capture`][sinatra-capture]: Let's you capture the content of blocks in templates. * [`sinatra/config_file`][sinatra-config-file]: Allows loading configuration from yaml files. * [`sinatra/content_for`][sinatra-content-for]: Adds Rails-style `content_for` helpers to Haml, Erb, Erubi, Erubis and Slim. * [`sinatra/cookies`][sinatra-cookies]: A `cookies` helper for reading and writing cookies. * [`sinatra/engine_tracking`][sinatra-engine-tracking]: Adds methods like `haml?` that allow helper methods to check whether they are called from within a template. * [`sinatra/json`][sinatra-json]: Adds a `#json` helper method to return JSON documents. * [`sinatra/link_header`][sinatra-link-header]: Helpers for generating `link` HTML tags and corresponding `Link` HTTP headers. Adds `link`, `stylesheet` and `prefetch` helper methods. * [`sinatra/multi_route`][sinatra-multi-route]: Adds ability to define one route block for multiple routes and multiple or custom HTTP verbs. * [`sinatra/namespace`][sinatra-namespace]: Adds namespace support to Sinatra. * [`sinatra/respond_with`][sinatra-respond-with]: Choose action and/or template automatically depending on the incoming request. Adds helpers `respond_to` and `respond_with`. * [`sinatra/custom_logger`][sinatra-custom-logger]: This extension allows you to define your own logger instance using +logger+ setting. That logger then will be available as #logger helper method in your routes and views. * [`sinatra/required_params`][sinatra-required-params]: Ensure if required query parameters exist ### Custom Extensions These extensions may add additional dependencies and enhance the behavior of the existing APIs. Currently included: * [`sinatra/reloader`][sinatra-reloader]: Automatically reloads Ruby files on code changes. ### Other Tools * [`sinatra/extension`][sinatra-extension]: Mixin for writing your own Sinatra extensions. * [`sinatra/test_helpers`][sinatra-test-helpers]: Helper methods to ease testing your Sinatra application. Partly extracted from Sinatra. Testing framework agnostic ## Installation Add `gem 'sinatra-contrib'` to *Gemfile*, then execute `bundle install`. If you don't use Bundler, install the gem manually by executing `gem install sinatra-contrib` in your command line. ### Git If you want to use the gem from git, for whatever reason, you can do the following: ```ruby github 'sinatra/sinatra' do gem 'sinatra-contrib' end ``` Within this block you can also specify other gems from this git repository. ## Usage ### Classic Style A single extension (example: sinatra-content-for): ``` ruby require 'sinatra' require 'sinatra/content_for' ``` Common extensions: ``` ruby require 'sinatra' require 'sinatra/contrib' ``` All extensions: ``` ruby require 'sinatra' require 'sinatra/contrib/all' ``` ### Modular Style A single extension (example: sinatra-content-for): ``` ruby require 'sinatra/base' require 'sinatra/content_for' require 'sinatra/namespace' class MyApp < Sinatra::Base # Note: Some modules are extensions, some helpers, see the specific # documentation or the source helpers Sinatra::ContentFor register Sinatra::Namespace end ``` Common extensions: ``` ruby require 'sinatra/base' require 'sinatra/contrib' class MyApp < Sinatra::Base register Sinatra::Contrib end ``` All extensions: ``` ruby require 'sinatra/base' require 'sinatra/contrib/all' class MyApp < Sinatra::Base register Sinatra::Contrib end ``` ### Documentation For more info check the [official docs](http://www.sinatrarb.com/contrib/) and [api docs](http://www.rubydoc.info/gems/sinatra-contrib). [sinatra-reloader]: http://www.sinatrarb.com/contrib/reloader [sinatra-namespace]: http://www.sinatrarb.com/contrib/namespace [sinatra-content-for]: http://www.sinatrarb.com/contrib/content_for [sinatra-cookies]: http://www.sinatrarb.com/contrib/cookies [sinatra-streaming]: http://www.sinatrarb.com/contrib/streaming [sinatra-webdav]: http://www.sinatrarb.com/contrib/webdav [sinatra-runner]: http://www.sinatrarb.com/contrib/runner [sinatra-extension]: http://www.sinatrarb.com/contrib/extension [sinatra-test-helpers]: https://github.com/sinatra/sinatra/blob/master/sinatra-contrib/lib/sinatra/test_helpers.rb [sinatra-required-params]: http://www.sinatrarb.com/contrib/required_params [sinatra-custom-logger]: http://www.sinatrarb.com/contrib/custom_logger [sinatra-multi-route]: http://www.sinatrarb.com/contrib/multi_route [sinatra-json]: http://www.sinatrarb.com/contrib/json [sinatra-respond-with]: http://www.sinatrarb.com/contrib/respond_with [sinatra-config-file]: http://www.sinatrarb.com/contrib/config_file [sinatra-link-header]: http://www.sinatrarb.com/contrib/link_header [sinatra-capture]: http://www.sinatrarb.com/contrib/capture [sinatra-engine-tracking]: https://github.com/sinatra/sinatra/blob/master/sinatra-contrib/lib/sinatra/engine_tracking.rb sinatra-2.0.8.1/sinatra-contrib/Rakefile000066400000000000000000000042411360317524000200670ustar00rootroot00000000000000$LOAD_PATH.unshift File.expand_path('../lib', __FILE__) require 'open-uri' require 'yaml' require 'sinatra/contrib/version' desc "run specs" task(:spec) { ruby '-S rspec spec -cw' } task(:test => :spec) task(:default => :spec) namespace :doc do task :readmes do Dir.glob 'lib/sinatra/*.rb' do |file| puts "Trying file... #{file}" excluded_files = %w[lib/sinatra/contrib.rb lib/sinatra/decompile.rb] next if excluded_files.include?(file) doc = File.read(file)[/^module Sinatra(\n)+( #[^\n]*\n)*/m].scan(/^ *#(?!#) ?(.*)\n/).join("\n") file = "doc/#{file[4..-4].tr("/_", "-")}.rdoc" Dir.mkdir "doc" unless File.directory? "doc" puts "writing #{file}" File.open(file, "w") { |f| f << doc } end end task :index do doc = File.read("README.md") file = "doc/sinatra-contrib-readme.md" Dir.mkdir "doc" unless File.directory? "doc" puts "writing #{file}" File.open(file, "w") { |f| f << doc } end task :all => [:readmes, :index] end desc "generate documentation" task :doc => 'doc:all' desc "generate gemspec" task 'sinatra-contrib.gemspec' do content = File.read 'sinatra-contrib.gemspec' fields = { :authors => `git shortlog -sn`.scan(/[^\d\s].*/), :email => `git shortlog -sne`.scan(/[^<]+@[^>]+/), :files => `git ls-files`.split("\n").reject { |f| f =~ /^(\.|Gemfile)/ } } fields.each do |field, values| updated = " s.#{field} = [" updated << values.map { |v| "\n %p" % v }.join(',') updated << "\n ]" content.sub!(/ s\.#{field} = \[\n( .*\n)* \]/, updated) end File.open('sinatra-contrib.gemspec', 'w') { |f| f << content } end task :gemspec => 'sinatra-contrib.gemspec' task :release => :gemspec do sh <<-SH rm -Rf sinatra-contrib*.gem && gem build sinatra-contrib.gemspec && gem install sinatra-contrib*.gem --local && gem push sinatra-contrib*.gem && git commit --allow-empty -a -m '#{Sinatra::Contrib::VERSION} release' && git tag -s v#{Sinatra::Contrib::VERSION} -m '#{Sinatra::Contrib::VERSION} release' && git push && (git push origin master || true) && git push --tags && (git push origin --tags || true) SH end sinatra-2.0.8.1/sinatra-contrib/ideas.md000066400000000000000000000017501360317524000200330ustar00rootroot00000000000000* Extension that does something like this: def build(*) if settings.memcached? use Rack::Cache, :backend => :memcached use Rack::Session::Memcached # ... end super end * `sinatra-smart-cache`: update cache header only if arguments are more restrictive than curent value, set caching headers that way for most helper methods (i.e. `sass` or `send_file`) * Some verbose logging extension: Log what filters, routes, error handlers, templates, and so on is used. * Form helpers, with forms as first class objects that accepts hashes or something, so the form meta data can also be used to expose a JSON API or similar, possibly defining routes (like "Sinatra's Hat"), strictly using the ActiveModel API. * Extend `sinatra-content-for` to support Liquid, Radius, Markaby, Nokogiri and Builder. At least the first two probably involve patching Tilt. * Rewrite of `sinatra-compass`? * Helpers for HTML escaping and such.sinatra-2.0.8.1/sinatra-contrib/lib/000077500000000000000000000000001360317524000171675ustar00rootroot00000000000000sinatra-2.0.8.1/sinatra-contrib/lib/sinatra/000077500000000000000000000000001360317524000206305ustar00rootroot00000000000000sinatra-2.0.8.1/sinatra-contrib/lib/sinatra/capture.rb000066400000000000000000000046601360317524000226260ustar00rootroot00000000000000require 'sinatra/base' require 'sinatra/engine_tracking' module Sinatra # # = Sinatra::Capture # # Extension that enables blocks inside other extensions. # It currently works for erb, slim and haml. # Enables mixing of different template languages. # # Example: # # # in hello_world.erb # # Say # <% a = capture do %>World<% end %> # Hello <%= a %>! # # # in hello_world.slim # # | Say # - a = capture do # | World # | Hello #{a}! # # # in hello_world.haml # # Say # - a = capture do # World # Hello #{a.strip}! # # # You can also use nested blocks. # # Example # # # in hello_world.erb # # Say # <% a = capture do %> # <% b = capture do %>World<% end %> # <%= b %>! # <% end %> # Hello <%= a.strip %> # # # The main advantage of capture is mixing of different template engines. # # Example # # # in mix_me_up.slim # # - two = capture do # - erb "<%= 1 + 1 %>" # | 1 + 1 = #{two} # # == Usage # # === Classic Application # # In a classic application simply require the helpers, and start using them: # # require "sinatra" # require "sinatra/capture" # # # The rest of your classic application code goes here... # # === Modular Application # # In a modular application you need to require the helpers, and then tell # the application you will use them: # # require "sinatra/base" # require "sinatra/capture" # # class MyApp < Sinatra::Base # helpers Sinatra::Capture # # # The rest of your modular application code goes here... # end # module Capture include Sinatra::EngineTracking def capture(*args, &block) return block[*args] if ruby? if haml? && Tilt[:haml] == Tilt::HamlTemplate buffer = Haml::Buffer.new(nil, Haml::Options.new.for_buffer) with_haml_buffer(buffer) { capture_haml(*args, &block) } else @_out_buf, _buf_was = '', @_out_buf begin raw = block[*args] captured = block.binding.eval('@_out_buf') captured.empty? ? raw : captured ensure @_out_buf = _buf_was end end end def capture_later(&block) engine = current_engine proc { |*a| with_engine(engine) { @capture = capture(*a, &block) } } end end helpers Capture end sinatra-2.0.8.1/sinatra-contrib/lib/sinatra/config_file.rb000066400000000000000000000120761360317524000234270ustar00rootroot00000000000000require 'sinatra/base' require 'yaml' require 'erb' module Sinatra # = Sinatra::ConfigFile # # Sinatra::ConfigFile is an extension that allows you to load the # application's configuration from YAML files. It automatically detects if # the files contain specific environment settings and it will use those # corresponding to the current one. # # You can access those options through +settings+ within the application. If # you try to get the value for a setting that hasn't been defined in the # config file for the current environment, you will get whatever it was set # to in the application. # # == Usage # # Once you have written your configurations to a YAML file you can tell the # extension to load them. See below for more information about how these # files are interpreted. # # For the examples, lets assume the following config.yml file: # # greeting: Welcome to my file configurable application # # === Classic Application # # require "sinatra" # require "sinatra/config_file" # # config_file 'path/to/config.yml' # # get '/' do # @greeting = settings.greeting # haml :index # end # # # The rest of your classic application code goes here... # # === Modular Application # # require "sinatra/base" # require "sinatra/config_file" # # class MyApp < Sinatra::Base # register Sinatra::ConfigFile # # config_file 'path/to/config.yml' # # get '/' do # @greeting = settings.greeting # haml :index # end # # # The rest of your modular application code goes here... # end # # === Config File Format # # In its most simple form this file is just a key-value list: # # foo: bar # something: 42 # nested: # a: 1 # b: 2 # # But it also can provide specific environment configuration. There are two # ways to do that: at the file level and at the settings level. # # At the settings level (e.g. in 'path/to/config.yml'): # # development: # foo: development # bar: bar # test: # foo: test # bar: bar # production: # foo: production # bar: bar # # Or at the file level: # # foo: # development: development # test: test # production: production # bar: bar # # In either case, settings.foo will return the environment name, and # settings.bar will return "bar". # # If you wish to provide defaults that may be shared among all the # environments, this can be done by using a YAML alias, and then overwriting # values in environments where appropriate: # # default: &common_settings # foo: 'foo' # bar: 'bar' # # production: # <<: *common_settings # bar: 'baz' # override the default value # module ConfigFile # When the extension is registered sets the +environments+ setting to the # traditional environments: development, test and production. def self.registered(base) base.set :environments, %w[test production development] end # Loads the configuration from the YAML files whose +paths+ are passed as # arguments, filtering the settings for the current environment. Note that # these +paths+ can actually be globs. def config_file(*paths) Dir.chdir(root || '.') do paths.each do |pattern| Dir.glob(pattern) do |file| raise UnsupportedConfigType unless ['.yml', '.yaml', '.erb'].include?(File.extname(file)) logger.info "loading config file '#{file}'" if logging? && respond_to?(:logger) document = ERB.new(IO.read(file)).result yaml = YAML.load(document) config = config_for_env(yaml) config.each_pair { |key, value| set(key, value) } end end end end class UnsupportedConfigType < Exception def message 'Invalid config file type, use .yml, .yaml or .erb' end end private # Given a +hash+ containing application configuration it returns # settings applicable to the current environment. Note: It gives # precedence to environment settings defined at the root-level. def config_for_env(hash) return from_environment_key(hash) if environment_keys?(hash) hash.each_with_object(IndifferentHash[]) do |(k, v), acc| if environment_keys?(v) acc.merge!(k => v[environment.to_s]) if v.key?(environment.to_s) else acc.merge!(k => v) end end end # Given a +hash+ returns the settings corresponding to the current # environment. def from_environment_key(hash) hash[environment.to_s] || hash[environment.to_sym] || {} end # Returns true if supplied with a hash that has any recognized # +environments+ in its root keys. def environment_keys?(hash) hash.is_a?(Hash) && hash.any? { |k, _| environments.include?(k.to_s) } end end register ConfigFile Delegator.delegate :config_file end sinatra-2.0.8.1/sinatra-contrib/lib/sinatra/content_for.rb000066400000000000000000000135131360317524000235000ustar00rootroot00000000000000require 'sinatra/base' require 'sinatra/capture' module Sinatra # = Sinatra::ContentFor # # Sinatra::ContentFor is a set of helpers that allows you to capture # blocks inside views to be rendered later during the request. The most # common use is to populate different parts of your layout from your view. # # The currently supported engines are: Erb, Erubi, Erubis, Haml and Slim. # # == Usage # # You call +content_for+, generally from a view, to capture a block of markup # giving it an identifier: # # # index.erb # <% content_for :some_key do %> # ... # <% end %> # # Then, you call +yield_content+ with that identifier, generally from a # layout, to render the captured block: # # # layout.erb # <%= yield_content :some_key %> # # If you have provided +yield_content+ with a block and no content for the # specified key is found, it will render the results of the block provided # to yield_content. # # # layout.erb # <% yield_content :some_key_with_no_content do %> # ... # <% end %> # # === Classic Application # # To use the helpers in a classic application all you need to do is require # them: # # require "sinatra" # require "sinatra/content_for" # # # Your classic application code goes here... # # === Modular Application # # To use the helpers in a modular application you need to require them, and # then, tell the application you will use them: # # require "sinatra/base" # require "sinatra/content_for" # # class MyApp < Sinatra::Base # helpers Sinatra::ContentFor # # # The rest of your modular application code goes here... # end # # == And How Is This Useful? # # For example, some of your views might need a few javascript tags and # stylesheets, but you don't want to force this files in all your pages. # Then you can put <%= yield_content :scripts_and_styles %> on your # layout, inside the tag, and each view can call content_for # setting the appropriate set of tags that should be added to the layout. # # == Limitations # # Due to the rendering process limitation using <%= yield_content %> # from within nested templates do not work above the <%= yield %> statement. # For more details https://github.com/sinatra/sinatra-contrib/issues/140#issuecomment-48831668 # # # app.rb # get '/' do # erb :body, :layout => :layout do # erb :foobar # end # end # # # foobar.erb # <% content_for :one do %> # # <% end %> # <% content_for :two do %> # # <% end %> # # Using <%= yield_content %> before <%= yield %> will cause only the second # alert to display: # # # body.erb # # Display only second alert # <%= yield_content :one %> # <%= yield %> # <%= yield_content :two %> # # # body.erb # # Display both alerts # <%= yield %> # <%= yield_content :one %> # <%= yield_content :two %> # module ContentFor include Capture # Capture a block of content to be rendered later. For example: # # <% content_for :head do %> # # <% end %> # # You can also pass an immediate value instead of a block: # # <% content_for :title, "foo" %> # # You can call +content_for+ multiple times with the same key # (in the example +:head+), and when you render the blocks for # that key all of them will be rendered, in the same order you # captured them. # # Your blocks can also receive values, which are passed to them # by yield_content def content_for(key, value = nil, options = {}, &block) block ||= proc { |*| value } clear_content_for(key) if options[:flush] content_blocks[key.to_sym] << capture_later(&block) end # Check if a block of content with the given key was defined. For # example: # # <% content_for :head do %> # # <% end %> # # <% if content_for? :head %> # content "head" was defined. # <% end %> def content_for?(key) content_blocks[key.to_sym].any? end # Unset a named block of content. For example: # # <% clear_content_for :head %> def clear_content_for(key) content_blocks.delete(key.to_sym) if content_for?(key) end # Render the captured blocks for a given key. For example: # # # Example # <%= yield_content :head %> # # # Would render everything you declared with content_for # :head before closing the tag. # # You can also pass values to the content blocks by passing them # as arguments after the key: # # <%= yield_content :head, 1, 2 %> # # Would pass 1 and 2 to all the blocks registered # for :head. def yield_content(key, *args, &block) if block_given? && !content_for?(key) (haml? && Tilt[:haml] == Tilt::HamlTemplate) ? capture_haml(*args, &block) : yield(*args) else content = content_blocks[key.to_sym].map { |b| capture(*args, &b) } content.join.tap do |c| if block_given? && (erb? || erubi? || erubis?) @_out_buf << c end end end end private def content_blocks @content_blocks ||= Hash.new { |h, k| h[k] = [] } end end helpers ContentFor end sinatra-2.0.8.1/sinatra-contrib/lib/sinatra/contrib.rb000066400000000000000000000016051360317524000226170ustar00rootroot00000000000000require 'sinatra/contrib/setup' module Sinatra module Contrib ## # Common middleware that doesn't bring run time overhead if not used # or breaks if external dependencies are missing. Will extend # Sinatra::Application by default. module Common register :ConfigFile register :MultiRoute register :Namespace register :RespondWith helpers :Capture helpers :ContentFor helpers :Cookies helpers :EngineTracking helpers :JSON helpers :LinkHeader helpers :Streaming helpers :RequiredParams end ## # Other extensions you don't want to be loaded unless needed. module Custom # register :Compass register :Reloader end ## # Stuff that aren't Sinatra extensions, technically. autoload :Extension autoload :TestHelpers end register Sinatra::Contrib::Common end sinatra-2.0.8.1/sinatra-contrib/lib/sinatra/contrib/000077500000000000000000000000001360317524000222705ustar00rootroot00000000000000sinatra-2.0.8.1/sinatra-contrib/lib/sinatra/contrib/all.rb000066400000000000000000000001011360317524000233550ustar00rootroot00000000000000require 'sinatra/contrib' Sinatra.register Sinatra::Contrib::All sinatra-2.0.8.1/sinatra-contrib/lib/sinatra/contrib/setup.rb000066400000000000000000000022021360317524000237510ustar00rootroot00000000000000require 'sinatra/base' require 'sinatra/contrib/version' require 'backports/rails/string' # for String#underscore module Sinatra module Contrib module Loader def extensions @extensions ||= {:helpers => [], :register => []} end def register(name, path = nil) autoload name, path, :register end def helpers(name, path = nil) autoload name, path, :helpers end def autoload(name, path = nil, method = nil) path ||= "sinatra/#{name.to_s.underscore}" extensions[method] << name if method Sinatra.autoload(name, path) end def registered(base) @extensions.each do |method, list| list = list.map { |name| Sinatra.const_get name } base.send(method, *list) unless base == ::Sinatra::Application end end end module Common extend Loader end module Custom extend Loader end module All def self.registered(base) base.register Common, Custom end end extend Loader def self.registered(base) base.register Common, Custom end end end sinatra-2.0.8.1/sinatra-contrib/lib/sinatra/contrib/version.rb000066400000000000000000000001031360317524000242740ustar00rootroot00000000000000module Sinatra module Contrib VERSION = '2.0.8.1' end end sinatra-2.0.8.1/sinatra-contrib/lib/sinatra/cookies.rb000066400000000000000000000156311360317524000226170ustar00rootroot00000000000000require 'sinatra/base' module Sinatra # = Sinatra::Cookies # # Easy way to deal with cookies # # == Usage # # Allows you to read cookies: # # get '/' do # "value: #{cookies[:something]}" # end # # And of course to write cookies: # # get '/set' do # cookies[:something] = 'foobar' # redirect to('/') # end # # And generally behaves like a hash: # # get '/demo' do # cookies.merge! 'foo' => 'bar', 'bar' => 'baz' # cookies.keep_if { |key, value| key.start_with? 'b' } # foo, bar = cookies.values_at 'foo', 'bar' # "size: #{cookies.length}" # end # # === Classic Application # # In a classic application simply require the helpers, and start using them: # # require "sinatra" # require "sinatra/cookies" # # # The rest of your classic application code goes here... # # === Modular Application # # In a modular application you need to require the helpers, and then tell # the application to use them: # # require "sinatra/base" # require "sinatra/cookies" # # class MyApp < Sinatra::Base # helpers Sinatra::Cookies # # # The rest of your modular application code goes here... # end # module Cookies class Jar include Enumerable attr_reader :options def initialize(app) @response_string = nil @response_hash = {} @response = app.response @request = app.request @deleted = [] @options = { :path => @request.script_name.to_s.empty? ? '/' : @request.script_name, :domain => @request.host == 'localhost' ? nil : @request.host, :secure => @request.secure?, :httponly => true } if app.settings.respond_to? :cookie_options @options.merge! app.settings.cookie_options end end def ==(other) other.respond_to? :to_hash and to_hash == other.to_hash end def [](key) response_cookies[key.to_s] || request_cookies[key.to_s] end def []=(key, value) set(key, value: value) end def assoc(key) to_hash.assoc(key.to_s) end if Hash.method_defined? :assoc def clear each_key { |k| delete(k) } end def compare_by_identity? false end def default nil end alias default_proc default def delete(key) result = self[key] @response.delete_cookie(key.to_s, @options) result end def delete_if return enum_for(__method__) unless block_given? each { |k, v| delete(k) if yield(k, v) } self end def each(&block) return enum_for(__method__) unless block_given? to_hash.each(&block) end def each_key(&block) return enum_for(__method__) unless block_given? to_hash.each_key(&block) end alias each_pair each def each_value(&block) return enum_for(__method__) unless block_given? to_hash.each_value(&block) end def empty? to_hash.empty? end def fetch(key, &block) response_cookies.fetch(key.to_s) do request_cookies.fetch(key.to_s, &block) end end def flatten to_hash.flatten end if Hash.method_defined? :flatten def has_key?(key) response_cookies.has_key? key.to_s or request_cookies.has_key? key.to_s end def has_value?(value) response_cookies.has_value? value or request_cookies.has_value? value end def hash to_hash.hash end alias include? has_key? alias member? has_key? def index(value) warn "Hash#index is deprecated; use Hash#key" key(value) end def inspect "<##{self.class}: #{to_hash.inspect[1..-2]}>" end def invert to_hash.invert end if Hash.method_defined? :invert def keep_if return enum_for(__method__) unless block_given? delete_if { |*a| not yield(*a) } end def key(value) to_hash.key(value) end alias key? has_key? def keys to_hash.keys end def length to_hash.length end def merge(other, &block) to_hash.merge(other, &block) end def merge!(other) other.each_pair do |key, value| if block_given? and include? key self[key] = yield(key.to_s, self[key], value) else self[key] = value end end end def rassoc(value) to_hash.rassoc(value) end def rehash response_cookies.rehash request_cookies.rehash self end def reject(&block) return enum_for(__method__) unless block_given? to_hash.reject(&block) end alias reject! delete_if def replace(other) select! { |k, v| other.include?(k) or other.include?(k.to_s) } merge! other end def select(&block) return enum_for(__method__) unless block_given? to_hash.select(&block) end alias select! keep_if if Hash.method_defined? :select! def set(key, options = {}) @response.set_cookie key.to_s, @options.merge(options) end def shift key, value = to_hash.shift delete(key) [key, value] end alias size length def sort(&block) to_hash.sort(&block) end if Hash.method_defined? :sort alias store []= def to_hash request_cookies.merge(response_cookies) end def to_a to_hash.to_a end def to_s to_hash.to_s end alias update merge! alias value? has_value? def values to_hash.values end def values_at(*list) list.map { |k| self[k] } end private def warn(message) super "#{caller.first[/^[^:]:\d+:/]} warning: #{message}" end def deleted parse_response @deleted end def response_cookies parse_response @response_hash end def parse_response string = @response['Set-Cookie'] return if @response_string == string hash = {} string.each_line do |line| key, value = line.split(';', 2).first.to_s.split('=', 2) next if key.nil? key = Rack::Utils.unescape(key) if line =~ /expires=Thu, 01[-\s]Jan[-\s]1970/ @deleted << key else @deleted.delete key hash[key] = value end end @response_hash.replace hash @response_string = string end def request_cookies @request.cookies.reject { |key, value| deleted.include? key } end end def cookies @cookies ||= Jar.new(self) end end helpers Cookies end sinatra-2.0.8.1/sinatra-contrib/lib/sinatra/custom_logger.rb000066400000000000000000000026511360317524000240320ustar00rootroot00000000000000module Sinatra # = Sinatra::CustomLogger # # CustomLogger extension allows you to define your own logger instance # using +logger+ setting. That logger then will be available # as #logger helper method in your routes and views. # # == Usage # # === Classic Application # # To define your custom logger instance in a classic application: # # require 'sinatra' # require 'sinatra/custom_logger' # require 'logger' # # set :logger, Logger.new(STDOUT) # # get '/' do # logger.info 'Some message' # STDOUT logger is used # # Other code... # end # # === Modular Application # # The same for modular application: # # require 'sinatra/base' # require 'sinatra/custom_logger' # require 'logger' # # class MyApp < Sinatra::Base # helpers Sinatra::CustomLogger # # configure :development, :production do # logger = Logger.new(File.open("#{root}/log/#{environment}.log", 'a')) # logger.level = Logger::DEBUG if development? # set :logger, logger # end # # get '/' do # logger.info 'Some message' # File-based logger is used # # Other code... # end # end # module CustomLogger def logger if settings.respond_to?(:logger) settings.logger else request.logger end end end helpers CustomLogger end sinatra-2.0.8.1/sinatra-contrib/lib/sinatra/decompile.rb000066400000000000000000000002201360317524000231100ustar00rootroot00000000000000warn "Sinatra::Decompile is deprecated without replacement." def warn(message) super "#{caller.first[/^[^:]:\d+:/]} warning: #{message}" end sinatra-2.0.8.1/sinatra-contrib/lib/sinatra/engine_tracking.rb000066400000000000000000000064161360317524000243130ustar00rootroot00000000000000require 'sinatra/base' module Sinatra # Adds methods like `haml?` that allow helper methods to check whether they # are called from within a template. module EngineTracking attr_reader :current_engine # @return [Boolean] Returns true if current engine is `:erb`. def erb? @current_engine == :erb end # Returns true if the current engine is `:erubi`, or `Tilt[:erb]` is set # to Tilt::ErubisTemplate. # # @return [Boolean] Returns true if current engine is `:erubi`. def erubi? @current_engine == :erubi or erb? && Tilt[:erb] == Tilt::ErubiTemplate end # Returns true if the current engine is `:erubis`, or `Tilt[:erb]` is set # to Tilt::ErubisTemplate. # # @return [Boolean] Returns true if current engine is `:erubis`. def erubis? @current_engine == :erubis or erb? && Tilt[:erb] == Tilt::ErubisTemplate end # @return [Boolean] Returns true if current engine is `:haml`. def haml? @current_engine == :haml end # @return [Boolean] Returns true if current engine is `:sass`. def sass? @current_engine == :sass end # @return [Boolean] Returns true if current engine is `:scss`. def scss? @current_engine == :scss end # @return [Boolean] Returns true if current engine is `:less`. def less? @current_engine == :less end # @return [Boolean] Returns true if current engine is `:builder`. def builder? @current_engine == :builder end # @return [Boolean] Returns true if current engine is `:liquid`. def liquid? @current_engine == :liquid end # @return [Boolean] Returns true if current engine is `:markdown`. def markdown? @current_engine == :markdown end # @return [Boolean] Returns true if current engine is `:textile. def textile? @current_engine == :textile end # @return [Boolean] Returns true if current engine is `:rdoc`. def rdoc? @current_engine == :rdoc end # @return [Boolean] Returns true if current engine is `:radius. def radius? @current_engine == :radius end # @return [Boolean] Returns true if current engine is `:markaby`. def markaby? @current_engine == :markaby end # @return [Boolean] Returns true if current engine is `:coffee`. def coffee? @current_engine == :coffee end # @return [Boolean] Returns true if current engine is `:nokogiri`. def nokogiri? @current_engine == :nokogiri end # @return [Boolean] Returns true if current engine is `:slim`. def slim? @current_engine == :slim end # @return [Boolean] Returns true if current engine is `:creole`. def creole? @current_engine == :creole end # @return [Boolean] Returns true if current engine is `:ruby`. def ruby? @current_engine == :ruby end def initialize(*) @current_engine = :ruby super end # @param engine [Symbol, String] Name of Engine to shift to. def with_engine(engine) @current_engine, engine_was = engine.to_sym, @current_engine yield ensure @current_engine = engine_was end private def render(engine, *) with_engine(engine) { super } end end helpers EngineTracking end sinatra-2.0.8.1/sinatra-contrib/lib/sinatra/extension.rb000066400000000000000000000050071360317524000231730ustar00rootroot00000000000000require 'sinatra/base' module Sinatra # = Sinatra::Extension # # Sinatra::Extension is a mixin that provides some syntactic sugar # for your extensions. It allows you to call almost any # Sinatra::Base method directly inside your extension # module. This means you can use +get+ to define a route, +before+ # to define a before filter, +set+ to define a setting and so on. # # Is important to be aware that this mixin remembers the method calls you # make, and then, when your extension is registered, replays them on the # Sinatra application that has been extended. In order to do that, it # defines a registered method, so, if your extension defines one # too, remember to call +super+. # # == Usage # # Just require the mixin and extend your extension with it: # # require 'sinatra/extension' # # module MyExtension # extend Sinatra::Extension # # # set some settings for development # configure :development do # set :reload_stuff, true # end # # # define a route # get '/' do # 'Hello World' # end # # # The rest of your extension code goes here... # end # # You can also create an extension with the +new+ method: # # MyExtension = Sinatra::Extension.new do # # Your extension code goes here... # end # # This is useful when you just want to pass a block to # Sinatra::Base.register. module Extension def self.new(&block) ext = Module.new.extend(self) ext.class_eval(&block) ext end def settings self end def configure(*args, &block) record(:configure, *args) { |c| c.instance_exec(c, &block) } end def registered(base = nil, &block) base ? replay(base) : record(:class_eval, &block) end private def record(method, *args, &block) recorded_methods << [method, args, block] end def replay(object) recorded_methods.each { |m, a, b| object.send(m, *a, &b) } end def recorded_methods @recorded_methods ||= [] end def method_missing(method, *args, &block) return super unless Sinatra::Base.respond_to? method record(method, *args, &block) DontCall.new(method) end class DontCall < BasicObject def initialize(method) @method = method end def method_missing(*) fail "not supposed to use result of #@method!" end def inspect; "#<#{self.class}: #{@method}>" end end end end sinatra-2.0.8.1/sinatra-contrib/lib/sinatra/json.rb000066400000000000000000000066421360317524000221360ustar00rootroot00000000000000require 'sinatra/base' require 'multi_json' module Sinatra # = Sinatra::JSON # # Sinatra::JSON adds a helper method, called +json+, for (obviously) # json generation. # # == Usage # # === Classic Application # # In a classic application simply require the helper, and start using it: # # require "sinatra" # require "sinatra/json" # # # define a route that uses the helper # get '/' do # json :foo => 'bar' # end # # # The rest of your classic application code goes here... # # === Modular Application # # In a modular application you need to require the helper, and then tell the # application you will use it: # # require "sinatra/base" # require "sinatra/json" # # class MyApp < Sinatra::Base # # # define a route that uses the helper # get '/' do # json :foo => 'bar' # end # # # The rest of your modular application code goes here... # end # # === Encoders # # By default it will try to call +to_json+ on the object, but if it doesn't # respond to that message, it will use its own rather simple encoder. You can # easily change that anyways. To use +JSON+, simply require it: # # require 'json' # # The same goes for Yajl::Encoder: # # require 'yajl' # # For other encoders, besides requiring them, you need to define the # :json_encoder setting. For instance, for the +Whatever+ encoder: # # require 'whatever' # set :json_encoder, Whatever # # To force +json+ to simply call +to_json+ on the object: # # set :json_encoder, :to_json # # Actually, it can call any method: # # set :json_encoder, :my_fancy_json_method # # === Content-Type # # It will automatically set the content type to "application/json". As # usual, you can easily change that, with the :json_content_type # setting: # # set :json_content_type, :js # # === Overriding the Encoder and the Content-Type # # The +json+ helper will also take two options :encoder and # :content_type. The values of this options are the same as the # :json_encoder and :json_content_type settings, # respectively. You can also pass those to the json method: # # get '/' do # json({:foo => 'bar'}, :encoder => :to_json, :content_type => :js) # end # module JSON class << self def encode(object) ::MultiJson.dump(object) end end def json(object, options = {}) content_type resolve_content_type(options) resolve_encoder_action object, resolve_encoder(options) end private def resolve_content_type(options = {}) options[:content_type] || settings.json_content_type end def resolve_encoder(options = {}) options[:json_encoder] || settings.json_encoder end def resolve_encoder_action(object, encoder) [:encode, :generate].each do |method| return encoder.send(method, object) if encoder.respond_to? method end if encoder.is_a? Symbol object.__send__(encoder) else fail "#{encoder} does not respond to #generate nor #encode" end #if end #resolve_encoder_action end #JSON Base.set :json_encoder do ::MultiJson end Base.set :json_content_type, :json # Load the JSON helpers in modular style automatically Base.helpers JSON end sinatra-2.0.8.1/sinatra-contrib/lib/sinatra/link_header.rb000066400000000000000000000072441360317524000234310ustar00rootroot00000000000000require 'sinatra/base' module Sinatra # = Sinatra::LinkHeader # # Sinatra::LinkHeader adds a set of helper methods to generate link # HTML tags and their corresponding Link HTTP headers. # # == Usage # # Once you had set up the helpers in your application (see below), you will # be able to call the following methods from inside your route handlers, # filters and templates: # # +prefetch+:: # Sets the Link HTTP headers and returns HTML tags to prefetch the given # resources. # # +stylesheet+:: # Sets the Link HTTP headers and returns HTML tags to use the given # stylesheets. # # +link+:: # Sets the Link HTTP headers and returns the corresponding HTML tags # for the given resources. # # +link_headers+:: # Returns the corresponding HTML tags for the current Link HTTP headers. # # === Classic Application # # In a classic application simply require the helpers, and start using them: # # require "sinatra" # require "sinatra/link_header" # # # The rest of your classic application code goes here... # # === Modular Application # # In a modular application you need to require the helpers, and then tell # the application you will use them: # # require "sinatra/base" # require "sinatra/link_header" # # class MyApp < Sinatra::Base # helpers Sinatra::LinkHeader # # # The rest of your modular application code goes here... # end # module LinkHeader ## # Sets Link HTTP header and returns HTML tags for telling the browser to # prefetch given resources (only supported by Opera and Firefox at the # moment). def prefetch(*urls) link(:prefetch, *urls) end ## # Sets Link HTTP header and returns HTML tags for using stylesheets. def stylesheet(*urls) urls << {} unless urls.last.respond_to? :to_hash urls.last[:type] ||= mime_type(:css) link(:stylesheet, *urls) end ## # Sets Link HTTP header and returns corresponding HTML tags. # # Example: # # # Sets header: # # Link: ; rel="next" # # Returns String: # # '' # link '/foo', :rel => :next # # # Multiple URLs # link :stylesheet, '/a.css', '/b.css' def link(*urls) opts = urls.last.respond_to?(:to_hash) ? urls.pop : {} opts[:rel] = urls.shift unless urls.first.respond_to? :to_str options = opts.map { |k, v| " #{k}=#{v.to_s.inspect}" } html_pattern = "" http_pattern = ["<%s>", *options].join ";" link = (response["Link"] ||= "") urls.map do |url| link << ",\n" unless link.empty? link << (http_pattern % url) html_pattern % url end.join "\n" end ## # Takes the current value of th Link header(s) and generates HTML tags # from it. # # Example: # # get '/' do # # You can of course use fancy helpers like #link, #stylesheet # # or #prefetch # response["Link"] = '; rel="next"' # haml :some_page # end # # __END__ # # @@ layout # %head= link_headers # %body= yield def link_headers yield if block_given? return "" unless response.include? "Link" response["Link"].split(",\n").map do |line| url, *opts = line.split(';').map(&:strip) "" end.join "\n" end def self.registered(base) puts "WARNING: #{self} is a helpers module, not an extension." end end helpers LinkHeader end sinatra-2.0.8.1/sinatra-contrib/lib/sinatra/multi_route.rb000066400000000000000000000041661360317524000235340ustar00rootroot00000000000000require 'sinatra/base' module Sinatra # = Sinatra::MultiRoute # # Create multiple routes with one statement. # # == Usage # # Use this extension to create a handler for multiple routes: # # get '/foo', '/bar' do # # ... # end # # Or for multiple verbs: # # route :get, :post, '/' do # # ... # end # # Or for multiple verbs and multiple routes: # # route :get, :post, ['/foo', '/bar'] do # # ... # end # # Or even for custom verbs: # # route 'LIST', '/' do # # ... # end # # === Classic Application # # To use the extension in a classic application all you need to do is require # it: # # require "sinatra" # require "sinatra/multi_route" # # # Your classic application code goes here... # # === Modular Application # # To use the extension in a modular application you need to require it, and # then, tell the application you will use it: # # require "sinatra/base" # require "sinatra/multi_route" # # class MyApp < Sinatra::Base # register Sinatra::MultiRoute # # # The rest of your modular application code goes here... # end # module MultiRoute def head(*args, &block) super(*route_args(args), &block) end def delete(*args, &block) super(*route_args(args), &block) end def get(*args, &block) super(*route_args(args), &block) end def options(*args, &block) super(*route_args(args), &block) end def patch(*args, &block) super(*route_args(args), &block) end def post(*args, &block) super(*route_args(args), &block) end def put(*args, &block) super(*route_args(args), &block) end def route(*args, &block) options = Hash === args.last ? args.pop : {} routes = [*args.pop] args.each do |verb| verb = verb.to_s.upcase if Symbol === verb routes.each do |route| super(verb, route, options, &block) end end end private def route_args(args) options = Hash === args.last ? args.pop : {} [args, options] end end register MultiRoute end sinatra-2.0.8.1/sinatra-contrib/lib/sinatra/namespace.rb000066400000000000000000000236421360317524000231200ustar00rootroot00000000000000require 'sinatra/base' require 'mustermann' module Sinatra # = Sinatra::Namespace # # Sinatra::Namespace is an extension that adds namespaces to an # application. This namespaces will allow you to share a path prefix for the # routes within the namespace, and define filters, conditions and error # handlers exclusively for them. Besides that, you can also register helpers # and extensions that will be used only within the namespace. # # == Usage # # Once you have loaded the extension (see below), you can use the +namespace+ # method to define namespaces in your application. # # You can define a namespace by a path prefix: # # namespace '/blog' do # get { haml :blog } # get '/:entry_permalink' do # @entry = Entry.find_by_permalink!(params[:entry_permalink]) # haml :entry # end # # # More blog routes... # end # # by a condition: # # namespace :host_name => 'localhost' do # get('/admin/dashboard') { haml :dashboard } # get('/admin/login') { haml :login } # # # More admin routes... # end # # or both: # # namespace '/admin', :host_name => 'localhost' do # get('/dashboard') { haml :dashboard } # get('/login') { haml :login } # post('/login') { login_user } # # # More admin routes... # end # # Regex is also accepted: # # namespace /\/posts\/([^\/&?]+)\// do # get { haml :blog } # # # More blog routes... # end # # When you define a filter or an error handler, or register an extension or a # set of helpers within a namespace, they only affect the routes defined in # it. For instance, lets define a before filter to prevent the access of # unauthorized users to the admin section of the application: # # namespace '/admin' do # helpers AdminHelpers # before { authenticate unless request.path_info == '/admin/login' } # # get '/dashboard' do # # Only authenticated users can access here... # haml :dashboard # end # # # More admin routes... # end # # get '/' do # # Any user can access here... # haml :index # end # # Well, they actually also affect the nested namespaces: # # namespace '/admin' do # helpers AdminHelpers # before { authenticate unless request.path_info == '/admin/login' } # # namespace '/users' do # get do # # Only authenticated users can access here... # @users = User.all # haml :users # end # # # More user admin routes... # end # # # More admin routes... # end # # Redirecting within the namespace can be done using redirect_to: # # namespace '/admin' do # get '/foo' do # redirect_to '/bar' # Redirects to /admin/bar # end # # get '/foo' do # redirect '/bar' # Redirects to /bar # end # end # # === Classic Application Setup # # To be able to use namespaces in a classic application all you need to do is # require the extension: # # require "sinatra" # require "sinatra/namespace" # # namespace '/users' do # end # # === Modular Application Setup # # To be able to use namespaces in a modular application all you need to do is # require the extension, and then, register it: # # require "sinatra/base" # require "sinatra/namespace" # # class MyApp < Sinatra::Base # register Sinatra::Namespace # # namespace '/users' do # end # end # # === Within an extension # # To be able to use namespaces within an extension, you need to first create # an extension. This includes defining the `registered(app)` method in the # module. # # require 'sinatra/base' # For creating Sinatra extensions # require 'sinatra/namespace' # To create namespaces # # module Zomg # Keep everything under "Zomg" namespace for sanity # module Routes # Define a new "Routes" module # # def self.registered(app) # # First, register the Namespace extension # app.register Sinatra::Namespace # # # This defines an `/api` namespace on the application # app.namespace '/api' do # get '/users' do # # do something with `GET "/api/users"` # end # end # # end # end # # # Lastly, register the extension to use in our app # Sinatra.register Routes # end # # In order to use this extension, is the same as any other Sinatra extension: # # module Zomg # # Define our app class, we use modular app for this example # class App < Sinatra::Base # # this gives us all the namespaces we defined earlier # register Routes # # get '/' do # "Welcome to my application!" # end # end # end # # Zomg::App.run! # Don't forget to start your app ;) # # Phew! That was a mouthful. # # I hope that helps you use `Sinatra::Namespace` in every way imaginable! # module Namespace def self.new(base, pattern, conditions = {}, &block) Module.new do #quelch uninitialized variable warnings, since these get used by compile method. @pattern, @conditions = nil, nil extend NamespacedMethods include InstanceMethods @base, @extensions, @errors = base, [], {} @pattern, @conditions = compile(pattern, conditions) @templates = Hash.new { |h,k| @base.templates[k] } namespace = self before { extend(@namespace = namespace) } class_eval(&block) end end module InstanceMethods def settings @namespace end def template_cache super.fetch(:nested, @namespace) { Tilt::Cache.new } end def redirect_to(uri, *args) redirect("#{@namespace.pattern}#{uri}", *args) end end module SharedMethods def namespace(pattern, conditions = {}, &block) Sinatra::Namespace.new(self, pattern, conditions, &block) end end module NamespacedMethods include SharedMethods attr_reader :base, :templates def self.prefixed(*names) names.each { |n| define_method(n) { |*a, &b| prefixed(n, *a, &b) }} end prefixed :before, :after, :delete, :get, :head, :options, :patch, :post, :put def helpers(*extensions, &block) class_eval(&block) if block_given? include(*extensions) if extensions.any? end 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 invoke_hook(name, *args) @extensions.each { |e| e.send(name, *args) if e.respond_to?(name) } end def not_found(&block) error(Sinatra::NotFound, &block) end def errors base.errors.merge(namespace_errors) end def namespace_errors @errors end def error(*codes, &block) args = Sinatra::Base.send(:compile!, "ERROR", /.*/, block) codes = codes.map { |c| Array(c) }.flatten codes << Exception if codes.empty? codes << Sinatra::NotFound if codes.include?(404) codes.each do |c| errors = @errors[c] ||= [] errors << args end end def respond_to(*args) return @conditions[:provides] || base.respond_to if args.empty? @conditions[:provides] = args end def set(key, value = self, &block) raise ArgumentError, "may not set #{key}" if key != :views return key.each { |k,v| set(k, v) } if block.nil? and value == self block ||= proc { value } singleton_class.send(:define_method, key, &block) end def enable(*opts) opts.each { |key| set(key, true) } end def disable(*opts) opts.each { |key| set(key, false) } end def template(name, &block) filename, line = caller_locations.first templates[name] = [block, filename, line.to_i] end def layout(name=:layout, &block) template name, &block end def pattern @pattern end private def app base.respond_to?(:base) ? base.base : base end def compile(pattern, conditions, default_pattern = nil) if pattern.respond_to? :to_hash conditions = conditions.merge pattern.to_hash pattern = nil end base_pattern, base_conditions = @pattern, @conditions pattern ||= default_pattern [ prefixed_path(base_pattern, pattern), (base_conditions || {}).merge(conditions) ] end def prefixed_path(a, b) return a || b || /.*/ unless a and b return Mustermann.new(b) if a == /.*/ Mustermann.new(a) + Mustermann.new(b) end def prefixed(method, pattern = nil, conditions = {}, &block) default = %r{(?:/.*)?} if method == :before or method == :after pattern, conditions = compile pattern, conditions, default result = base.send(method, pattern, conditions, &block) invoke_hook :route_added, method.to_s.upcase, pattern, block result end def method_missing(method, *args, &block) base.send(method, *args, &block) end def respond_to?(method, include_private = false) super || base.respond_to?(method, include_private) end end module BaseMethods include SharedMethods end def self.extended(base) base.extend BaseMethods end end register Sinatra::Namespace Delegator.delegate :namespace end sinatra-2.0.8.1/sinatra-contrib/lib/sinatra/reloader.rb000066400000000000000000000326031360317524000227560ustar00rootroot00000000000000require 'sinatra/base' module Sinatra # = Sinatra::Reloader # # Extension to reload modified files. Useful during development, # since it will automatically require files defining routes, filters, # error handlers and inline templates, with every incoming request, # but only if they have been updated. # # == Usage # # === Classic Application # # To enable the reloader in a classic application all you need to do is # require it: # # require "sinatra" # require "sinatra/reloader" if development? # # # Your classic application code goes here... # # === Modular Application # # To enable the reloader in a modular application all you need to do is # require it, and then, register it: # # require "sinatra/base" # require "sinatra/reloader" # # class MyApp < Sinatra::Base # configure :development do # register Sinatra::Reloader # end # # # Your modular application code goes here... # end # # == Using the Reloader in Other Environments # # By default, the reloader is only enabled for the development # environment. Similar to registering the reloader in a modular # application, a classic application requires manually enabling the # extension for it to be available in a non-development environment. # # require "sinatra" # require "sinatra/reloader" # # configure :production do # enable :reloader # end # # == Changing the Reloading Policy # # You can refine the reloading policy with +also_reload+ and # +dont_reload+, to customize which files should, and should not, be # reloaded, respectively. You can also use +after_reload+ to execute a # block after any file being reloaded. # # === Classic Application # # Simply call the methods: # # require "sinatra" # require "sinatra/reloader" if development? # # also_reload '/path/to/some/file' # dont_reload '/path/to/other/file' # after_reload do # puts 'reloaded' # end # # # Your classic application code goes here... # # === Modular Application # # Call the methods inside the +configure+ block: # # require "sinatra/base" # require "sinatra/reloader" # # class MyApp < Sinatra::Base # configure :development do # register Sinatra::Reloader # also_reload '/path/to/some/file' # dont_reload '/path/to/other/file' # after_reload do # puts 'reloaded' # end # end # # # Your modular application code goes here... # end # module Reloader # Watches a file so it can tell when it has been updated, and what # elements does it contain. class Watcher # Represents an element of a Sinatra application that may need to # be reloaded. An element could be: # * a route # * a filter # * an error handler # * a middleware # * inline templates # # Its +representation+ attribute is there to allow to identify the # element within an application, that is, to match it with its # Sinatra's internal representation. class Element < Struct.new(:type, :representation) end # Collection of file +Watcher+ that can be associated with a # Sinatra application. That way, we can know which files belong # to a given application and which files have been modified. It # also provides a mechanism to inform a Watcher of the elements # defined in the file being watched and if its changes should be # ignored. class List @app_list_map = Hash.new { |hash, key| hash[key] = new } # Returns the +List+ for the application +app+. def self.for(app) @app_list_map[app] end # Creates a new +List+ instance. def initialize @path_watcher_map = Hash.new do |hash, key| hash[key] = Watcher.new(key) end end # Lets the +Watcher+ for the file located at +path+ know that the # +element+ is defined there, and adds the +Watcher+ to the +List+, # if it isn't already there. def watch(path, element) watcher_for(path).elements << element end # Tells the +Watcher+ for the file located at +path+ to ignore # the file changes, and adds the +Watcher+ to the +List+, if # it isn't already there. def ignore(path) watcher_for(path).ignore end # Adds a +Watcher+ for the file located at +path+ to the # +List+, if it isn't already there. def watcher_for(path) @path_watcher_map[File.expand_path(path)] end alias watch_file watcher_for # Returns an array with all the watchers in the +List+. def watchers @path_watcher_map.values end # Returns an array with all the watchers in the +List+ that # have been updated. def updated watchers.find_all(&:updated?) end end attr_reader :path, :elements, :mtime # Creates a new +Watcher+ instance for the file located at +path+. def initialize(path) @ignore = nil @path, @elements = path, [] update end # Indicates whether or not the file being watched has been modified. def updated? !ignore? && !removed? && mtime != File.mtime(path) end # Updates the mtime of the file being watched. def update @mtime = File.mtime(path) end # Indicates whether or not the file being watched has inline # templates. def inline_templates? elements.any? { |element| element.type == :inline_templates } end # Informs that the modifications to the file being watched # should be ignored. def ignore @ignore = true end # Indicates whether or not the modifications to the file being # watched should be ignored. def ignore? !!@ignore end # Indicates whether or not the file being watched has been removed. def removed? !File.exist?(path) end end MUTEX_FOR_PERFORM = Mutex.new # Allow a block to be executed after any file being reloaded @@after_reload = [] def after_reload(&block) @@after_reload << block end # When the extension is registered it extends the Sinatra application # +klass+ with the modules +BaseMethods+ and +ExtensionMethods+ and # defines a before filter to +perform+ the reload of the modified files. def self.registered(klass) @reloader_loaded_in ||= {} return if @reloader_loaded_in[klass] @reloader_loaded_in[klass] = true klass.extend BaseMethods klass.extend ExtensionMethods klass.set(:reloader) { klass.development? } klass.set(:reload_templates) { klass.reloader? } klass.before do if klass.reloader? MUTEX_FOR_PERFORM.synchronize { Reloader.perform(klass) } end end klass.set(:inline_templates, klass.app_file) if klass == Sinatra::Application end # Reloads the modified files, adding, updating and removing the # needed elements. def self.perform(klass) Watcher::List.for(klass).updated.each do |watcher| klass.set(:inline_templates, watcher.path) if watcher.inline_templates? watcher.elements.each { |element| klass.deactivate(element) } $LOADED_FEATURES.delete(watcher.path) require watcher.path watcher.update end @@after_reload.each(&:call) end # Contains the methods defined in Sinatra::Base that are overridden. module BaseMethods # Protects Sinatra::Base.run! from being called more than once. def run!(*args) if settings.reloader? super unless running? else super end end # Does everything Sinatra::Base#route does, but it also tells the # +Watcher::List+ for the Sinatra application to watch the defined # route. # # Note: We are using #compile! so we don't interfere with extensions # changing #route. def compile!(verb, path, block, options = {}) source_location = block.respond_to?(:source_location) ? block.source_location.first : caller_files[1] signature = super watch_element( source_location, :route, { :verb => verb, :signature => signature } ) signature end # Does everything Sinatra::Base#inline_templates= does, but it also # tells the +Watcher::List+ for the Sinatra application to watch the # inline templates in +file+ or the file who made the call to this # method. def inline_templates=(file=nil) file = (file.nil? || file == true) ? (caller_files[1] || File.expand_path($0)) : file watch_element(file, :inline_templates) super end # Does everything Sinatra::Base#use does, but it also tells the # +Watcher::List+ for the Sinatra application to watch the middleware # being used. def use(middleware, *args, &block) path = caller_files[1] || File.expand_path($0) watch_element(path, :middleware, [middleware, args, block]) super end # Does everything Sinatra::Base#add_filter does, but it also tells # the +Watcher::List+ for the Sinatra application to watch the defined # filter. def add_filter(type, path = nil, options = {}, &block) source_location = block.respond_to?(:source_location) ? block.source_location.first : caller_files[1] result = super watch_element(source_location, :"#{type}_filter", filters[type].last) result end # Does everything Sinatra::Base#error does, but it also tells the # +Watcher::List+ for the Sinatra application to watch the defined # error handler. def error(*codes, &block) path = caller_files[1] || File.expand_path($0) result = super codes.each do |c| watch_element(path, :error, :code => c, :handler => @errors[c]) end result end # Does everything Sinatra::Base#register does, but it also lets the # reloader know that an extension is being registered, because the # elements defined in its +registered+ method need a special treatment. def register(*extensions, &block) start_registering_extension result = super stop_registering_extension result end # Does everything Sinatra::Base#register does and then registers the # reloader in the +subclass+. def inherited(subclass) result = super subclass.register Sinatra::Reloader result end end # Contains the methods that the extension adds to the Sinatra application. module ExtensionMethods # Removes the +element+ from the Sinatra application. def deactivate(element) case element.type when :route then verb = element.representation[:verb] signature = element.representation[:signature] (routes[verb] ||= []).delete(signature) when :middleware then @middleware.delete(element.representation) when :before_filter then filters[:before].delete(element.representation) when :after_filter then filters[:after].delete(element.representation) when :error then code = element.representation[:code] handler = element.representation[:handler] @errors.delete(code) if @errors[code] == handler end end # Indicates with a +glob+ which files should be reloaded if they # have been modified. It can be called several times. def also_reload(*glob) Dir[*glob].each { |path| Watcher::List.for(self).watch_file(path) } end # Indicates with a +glob+ which files should not be reloaded even if # they have been modified. It can be called several times. def dont_reload(*glob) Dir[*glob].each { |path| Watcher::List.for(self).ignore(path) } end private # attr_reader :register_path warn on -w (private attribute) def register_path; @register_path ||= nil; end # Indicates an extesion is being registered. def start_registering_extension @register_path = caller_files[2] end # Indicates the extesion has already been registered. def stop_registering_extension @register_path = nil end # Indicates whether or not an extension is being registered. def registering_extension? !register_path.nil? end # Builds a Watcher::Element from +type+ and +representation+ and # tells the Watcher::List for the current application to watch it # in the file located at +path+. # # If an extension is being registered, it also tells the list to # watch it in the file where the extension has been registered. # This prevents the duplication of the elements added by the # extension in its +registered+ method with every reload. def watch_element(path, type, representation=nil) list = Watcher::List.for(self) element = Watcher::Element.new(type, representation) list.watch(path, element) list.watch(register_path, element) if registering_extension? end end end register Reloader Delegator.delegate :also_reload, :dont_reload end sinatra-2.0.8.1/sinatra-contrib/lib/sinatra/required_params.rb000066400000000000000000000032001360317524000243330ustar00rootroot00000000000000require 'sinatra/base' module Sinatra # = Sinatra::RequiredParams # # Ensure required query parameters # # == Usage # # Set required query parameter keys in the argument. # It'll halt with 400 if required keys don't exist. # # get '/simple_keys' do # required_params :p1, :p2 # end # # Complicated pattern is also fine. # # get '/complicated_keys' do # required_params :p1, :p2 => [:p3, :p4] # end # # === Classic Application # # In a classic application simply require the helpers, and start using them: # # require "sinatra" # require "sinatra/required_params" # # # The rest of your classic application code goes here... # # === Modular Application # # In a modular application you need to require the helpers, and then tell # the application to use them: # # require "sinatra/base" # require "sinatra/required_params" # # class MyApp < Sinatra::Base # helpers Sinatra::RequiredParams # # # The rest of your modular application code goes here... # end # module RequiredParams def required_params(*keys) _required_params(params, *keys) end private def _required_params(p, *keys) keys.each do |key| if key.is_a?(Hash) _required_params(p, *key.keys) key.each do |k, v| _required_params(p[k.to_s], v) end elsif key.is_a?(Array) _required_params(p, *key) else halt 400 unless p && p.respond_to?(:has_key?) && p.has_key?(key.to_s) end end true end end helpers RequiredParams end sinatra-2.0.8.1/sinatra-contrib/lib/sinatra/respond_with.rb000066400000000000000000000176161360317524000236750ustar00rootroot00000000000000require 'sinatra/json' require 'sinatra/base' module Sinatra # # = Sinatra::RespondWith # # These extensions let Sinatra automatically choose what template to render or # action to perform depending on the request's Accept header. # # Example: # # # Without Sinatra::RespondWith # get '/' do # data = { :name => 'example' } # request.accept.each do |type| # case type.to_s # when 'text/html' # halt haml(:index, :locals => data) # when 'text/json' # halt data.to_json # when 'application/atom+xml' # halt nokogiri(:'index.atom', :locals => data) # when 'application/xml', 'text/xml' # halt nokogiri(:'index.xml', :locals => data) # when 'text/plain' # halt 'just an example' # end # end # error 406 # end # # # With Sinatra::RespondWith # get '/' do # respond_with :index, :name => 'example' do |f| # f.txt { 'just an example' } # end # end # # Both helper methods +respond_to+ and +respond_with+ let you define custom # handlers like the one above for +text/plain+. +respond_with+ additionally # takes a template name and/or an object to offer the following default # behavior: # # * If a template name is given, search for a template called # +name.format.engine+ (+index.xml.nokogiri+ in the above example). # * If a template name is given, search for a templated called +name.engine+ # for engines known to result in the requested format (+index.haml+). # * If a file extension associated with the mime type is known to Sinatra, and # the object responds to +to_extension+, call that method and use the result # (+data.to_json+). # # == Security # # Since methods are triggered based on client input, this can lead to security # issues (but not as severe as those might appear in the first place: keep in # mind that only known file extensions are used). You should limit # the possible formats you serve. # # This is possible with the +provides+ condition: # # get '/', :provides => [:html, :json, :xml, :atom] do # respond_with :index, :name => 'example' # end # # However, since you have to set +provides+ for every route, this extension # adds an app global (class method) `respond_to`, that lets you define content # types for all routes: # # respond_to :html, :json, :xml, :atom # get('/a') { respond_with :index, :name => 'a' } # get('/b') { respond_with :index, :name => 'b' } # # == Custom Types # # Use the +on+ method for defining actions for custom types: # # get '/' do # respond_to do |f| # f.xml { nokogiri :index } # f.on('application/custom') { custom_action } # f.on('text/*') { data.to_s } # f.on('*/*') { "matches everything" } # end # end # # Definition order does not matter. module RespondWith class Format def initialize(app) @app, @map, @generic, @default = app, {}, {}, nil end def on(type, &block) @app.settings.mime_types(type).each do |mime| case mime when '*/*' then @default = block when /^([^\/]+)\/\*$/ then @generic[$1] = block else @map[mime] = block end end end def finish yield self if block_given? mime_type = @app.content_type || @app.request.preferred_type(@map.keys) || @app.request.preferred_type || 'text/html' type = mime_type.split(/\s*;\s*/, 2).first handlers = [@map[type], @generic[type[/^[^\/]+/]], @default].compact handlers.each do |block| if result = block.call(type) @app.content_type mime_type @app.halt result end end @app.halt 500, "Unknown template engine" end def method_missing(method, *args, &block) return super if args.any? or block.nil? or not @app.mime_type(method) on(method, &block) end end module Helpers include Sinatra::JSON def respond_with(template, object = nil, &block) object, template = template, nil unless Symbol === template format = Format.new(self) format.on "*/*" do |type| exts = settings.ext_map[type] exts << :xml if type.end_with? '+xml' if template args = template_cache.fetch(type, template) { template_for(template, exts) } if args.any? locals = { :object => object } locals.merge! object.to_hash if object.respond_to? :to_hash renderer = args.first options = args[1..-1] + [{:locals => locals}] halt send(renderer, *options) end end if object exts.each do |ext| halt json(object) if ext == :json next unless object.respond_to? method = "to_#{ext}" halt(*object.send(method)) end end false end format.finish(&block) end def respond_to(&block) Format.new(self).finish(&block) end private def template_for(name, exts) # in production this is cached, so don't worry too much about runtime possible = [] settings.template_engines[:all].each do |engine| exts.each { |ext| possible << [engine, "#{name}.#{ext}"] } end exts.each do |ext| settings.template_engines[ext].each { |e| possible << [e, name] } end possible.each do |engine, template| klass = Tilt.default_mapping.template_map[engine.to_s] || Tilt.lazy_map[engine.to_s].fetch(0, [])[0] find_template(settings.views, template, klass) do |file| next unless File.exist? file return settings.rendering_method(engine) << template.to_sym end end [] # nil or false would not be cached end end def remap_extensions ext_map.clear Rack::Mime::MIME_TYPES.each { |e,t| ext_map[t] << e[1..-1].to_sym } ext_map['text/javascript'] << 'js' ext_map['text/xml'] << 'xml' end def mime_type(*) result = super remap_extensions result end def respond_to(*formats) @respond_to ||= nil if formats.any? @respond_to ||= [] @respond_to.concat formats elsif @respond_to.nil? and superclass.respond_to? :respond_to superclass.respond_to else @respond_to end end def rendering_method(engine) return [engine] if Sinatra::Templates.method_defined? engine return [:mab] if engine.to_sym == :markaby [:render, :engine] end private def compile!(verb, path, block, options = {}) options[:provides] ||= respond_to if respond_to super end def self.jrubyify(engs) not_supported = [:markdown] engs.keys.each do |key| engs[key].collect! { |eng| (eng == :yajl) ? :json_pure : eng } engs[key].delete_if { |eng| not_supported.include?(eng) } end engs end def self.engines engines = { :css => [:less, :sass, :scss], :xml => [:builder, :nokogiri], :js => [:coffee], :html => [:erb, :erubi, :erubis, :haml, :halmit, :slim, :liquid, :radius, :mab, :markdown, :textile, :rdoc], :all => (Sinatra::Templates.instance_methods.map(&:to_sym) + [:mab] - [:find_template, :markaby]), :json => [:yajl], } engines.default = [] (defined? JRUBY_VERSION) ? jrubyify(engines) : engines end def self.registered(base) base.set :ext_map, Hash.new { |h,k| h[k] = [] } base.set :template_engines, engines base.remap_extensions base.helpers Helpers end end register RespondWith Delegator.delegate :respond_to end sinatra-2.0.8.1/sinatra-contrib/lib/sinatra/runner.rb000066400000000000000000000070321360317524000224700ustar00rootroot00000000000000require 'open-uri' require 'net/http' require 'timeout' module Sinatra # NOTE: This feature is experimental, and missing tests! # # Helps you spinning up and shutting down your own sinatra app. This is especially helpful for running # real network tests against a sinatra backend. # # The backend server could look like the following (in test/server.rb). # # require "sinatra" # # get "/" do # "Cheers from test server" # end # # get "/ping" do # "1" # end # # Note that you need to implement a ping action for internal use. # # Next, you need to write your runner. # # require 'sinatra/runner' # # class Runner < Sinatra::Runner # def app_file # File.expand_path("../server.rb", __FILE__) # end # end # # Override Runner#app_file, #command, #port, #protocol and #ping_path for customization. # # **Don't forget to override #app_file specific to your application!** # # Wherever you need this test backend, here's how you manage it. The following example assumes you # have a test in your app that needs to be run against your test backend. # # runner = ServerRunner.new # runner.run # # # ..tests against localhost:4567 here.. # # runner.kill # # For an example, check https://github.com/apotonick/roar/blob/master/test/integration/runner.rb class Runner def app_file File.expand_path("../server.rb", __FILE__) end def run @pipe = start @started = Time.now warn "#{server} up and running on port #{port}" if ping end def kill return unless pipe Process.kill("KILL", pipe.pid) rescue NotImplementedError system "kill -9 #{pipe.pid}" rescue Errno::ESRCH end def get(url) Timeout.timeout(1) { get_url("#{protocol}://127.0.0.1:#{port}#{url}") } end def get_stream(url = "/stream", &block) Net::HTTP.start '127.0.0.1', port do |http| request = Net::HTTP::Get.new url http.request request do |response| response.read_body(&block) end end end def get_response(url) Net::HTTP.start '127.0.0.1', port do |http| request = Net::HTTP::Get.new url http.request request do |response| response end end end def log @log ||= "" loop { @log << pipe.read_nonblock(1) } rescue Exception @log end private attr_accessor :pipe def start IO.popen(command) end def command # to be overwritten "bundle exec ruby #{app_file} -p #{port} -e production" end def ping(timeout=30) loop do return if alive? if Time.now - @started > timeout $stderr.puts command, log fail "timeout" else sleep 0.1 end end end def alive? 3.times { get(ping_path) } true rescue Errno::ECONNREFUSED, Errno::ECONNRESET, EOFError, SystemCallError, OpenURI::HTTPError, Timeout::Error false end def ping_path # to be overwritten '/ping' end def port # to be overwritten 4567 end def protocol "http" end def get_url(url) uri = URI.parse(url) return uri.read unless protocol == "https" get_https_url(uri) end def get_https_url(uri) http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true http.verify_mode = OpenSSL::SSL::VERIFY_NONE request = Net::HTTP::Get.new(uri.request_uri) http.request(request).body end end end sinatra-2.0.8.1/sinatra-contrib/lib/sinatra/streaming.rb000066400000000000000000000132421360317524000231500ustar00rootroot00000000000000require 'sinatra/base' module Sinatra # = Sinatra::Streaming # # Sinatra 1.3 introduced the +stream+ helper. This addon improves the # streaming API by making the stream object imitate an IO object, turning # it into a real Deferrable and making the body play nicer with middleware # unaware of streaming. # # == IO-like behavior # # This is useful when passing the stream object to a library expecting an # IO or StringIO object. # # get '/' do # stream do |out| # out.puts "Hello World!", "How are you?" # out.write "Written #{out.pos} bytes so far!\n" # out.putc(65) unless out.closed? # out.flush # end # end # # == Better Middleware Handling # # Blocks passed to #map! or #map will actually be applied when streaming # takes place (as you might have suspected, #map! applies modifications # to the current body, while #map creates a new one): # # class StupidMiddleware # def initialize(app) @app = app end # # def call(env) # status, headers, body = @app.call(env) # body.map! { |e| e.upcase } # [status, headers, body] # end # end # # use StupidMiddleware # # get '/' do # stream do |out| # out.puts "still" # sleep 1 # out.puts "streaming" # end # end # # Even works if #each is used to generate an Enumerator: # # def call(env) # status, headers, body = @app.call(env) # body = body.each.map { |s| s.upcase } # [status, headers, body] # end # # Note that both examples violate the Rack specification. # # == Setup # # In a classic application: # # require "sinatra" # require "sinatra/streaming" # # In a modular application: # # require "sinatra/base" # require "sinatra/streaming" # # class MyApp < Sinatra::Base # helpers Sinatra::Streaming # end module Streaming def stream(*) stream = super stream.extend Stream stream.app = self env['async.close'].callback { stream.close } if env.key? 'async.close' stream end module Stream attr_accessor :app, :lineno, :pos, :transformer, :closed alias tell pos alias closed? closed def self.extended(obj) obj.closed, obj.lineno, obj.pos = false, 0, 0 obj.callback { obj.closed = true } obj.errback { obj.closed = true } end def <<(data) raise IOError, 'not opened for writing' if closed? @transformer ||= nil data = data.to_s data = @transformer[data] if @transformer @pos += data.bytesize super(data) end def each # that way body.each.map { ... } works return self unless block_given? super end def map(&block) # dup would not copy the mixin clone.map!(&block) end def map!(&block) @transformer ||= nil if @transformer inner, outer = @transformer, block block = proc { |value| outer[inner[value]] } end @transformer = block self end def write(data) self << data data.to_s.bytesize end alias syswrite write alias write_nonblock write def print(*args) args.each { |arg| self << arg } nil end def printf(format, *args) print(format.to_s % args) end def putc(c) print c.chr end def puts(*args) args.each { |arg| self << "#{arg}\n" } nil end def close_read raise IOError, "closing non-duplex IO for reading" end def closed_read? true end def closed_write? closed? end def external_encoding Encoding.find settings.default_encoding rescue NameError settings.default_encoding end def closed? @closed end def settings app.settings end def rewind @pos = @lineno = 0 end def not_open_for_reading(*) raise IOError, "not opened for reading" end alias bytes not_open_for_reading alias eof? not_open_for_reading alias eof not_open_for_reading alias getbyte not_open_for_reading alias getc not_open_for_reading alias gets not_open_for_reading alias read not_open_for_reading alias read_nonblock not_open_for_reading alias readbyte not_open_for_reading alias readchar not_open_for_reading alias readline not_open_for_reading alias readlines not_open_for_reading alias readpartial not_open_for_reading alias sysread not_open_for_reading alias ungetbyte not_open_for_reading alias ungetc not_open_for_reading private :not_open_for_reading def enum_not_open_for_reading(*) not_open_for_reading if block_given? enum_for(:not_open_for_reading) end alias chars enum_not_open_for_reading alias each_line enum_not_open_for_reading alias each_byte enum_not_open_for_reading alias each_char enum_not_open_for_reading alias lines enum_not_open_for_reading undef enum_not_open_for_reading def dummy(*) end alias flush dummy alias fsync dummy alias internal_encoding dummy alias pid dummy undef dummy def seek(*) 0 end alias sysseek seek def sync true end def tty? false end alias isatty tty? end end helpers Streaming end sinatra-2.0.8.1/sinatra-contrib/lib/sinatra/test_helpers.rb000066400000000000000000000136441360317524000236660ustar00rootroot00000000000000require 'sinatra/base' require 'rack' begin require 'rack/test' rescue LoadError abort 'Add rack-test to your Gemfile to use this module!' end require 'forwardable' module Sinatra Base.set :environment, :test # Helper methods to ease testing your Sinatra application. Partly extracted # from Sinatra. Testing framework agnostic. module TestHelpers # Test variant of session, which exposes a `global_env`. class Session < Rack::Test::Session def global_env @global_env ||= {} end private def default_env super.merge global_env end end include Rack::Test::Methods extend Forwardable attr_accessor :settings # @!group Instance Methods delegated to last_response # @!method body # # Body of last_response # # @see http://www.rubydoc.info/github/rack/rack/master/Rack/Response#body-instance_method # @return [String] body of the last response # @!method headers # # Headers of last_response # # @return [Hash] hash of the last response # @!method status # # HTTP status of last_response # # @return [Integer] HTTP status of the last response # @!method errors # # Errors of last_response # # @return [Array] errors of the last response def_delegators :last_response, :body, :headers, :status, :errors # @!endgroup # @!group Class Methods delegated to app # @!method configure(*envs) {|_self| ... } # @!scope class # @yieldparam _self [Sinatra::Base] the object that the method was called on # # Set configuration options for Sinatra and/or the app. Allows scoping of # settings for certain environments. # @!method set(option, value = (not_set = true), ignore_setter = false, &block) # @!scope class # Sets an option to the given value. If the value is a proc, the proc will # be called every time the option is accessed. # @raise [ArgumentError] # @!method enable(*opts) # @!scope class # # Same as calling `set :option, true` for each of the given options. # @!method disable(*opts) # @!scope class # # Same as calling `set :option, false` for each of the given options. # @!method use(middleware, *args, &block) # @!scope class # Use the specified Rack middleware # @!method helpers(*extensions, &block) # @!scope class # # Makes the methods defined in the block and in the Modules given in # `extensions` available to the handlers and templates. # @!method register(*extensions, &block) # @!scope class # Register an extension. Alternatively take a block from which an # extension will be created and registered on the fly. def_delegators :app, :configure, :set, :enable, :disable, :use, :helpers, :register # @!endgroup # @!group Instance Methods delegated to current_session # @!method env_for(uri = "", opts = {}) # # Return the Rack environment used for a request to `uri`. # # @return [Hash] def_delegators :current_session, :env_for # @!endgroup # @!group Instance Methods delegated to rack_mock_session # @!method cookie_jar # # Returns a {http://www.rubydoc.info/github/rack-test/rack-test/Rack/Test/CookieJar Rack::Test::CookieJar}. # # @return [Rack::Test::CookieJar] def_delegators :rack_mock_session, :cookie_jar # @!endgroup # Instantiate and configure a mock Sinatra app. # # Takes a `base` app class, or defaults to Sinatra::Base, and instantiates # an app instance. Any given code in `block` is `class_eval`'d on this new # instance before the instance is returned. # # @param base [Sinatra::Base] App base class # # @return [Sinatra] Configured mocked app def mock_app(base = Sinatra::Base, &block) inner = nil @app = Sinatra.new(base) do inner = self class_eval(&block) end @settings = inner app end # Replaces the configured app. # # @param base [Sinatra::Base] a configured app def app=(base) @app = base end alias set_app app= # Returns a Rack::Lint-wrapped Sinatra app. # # If no app has been configured, a new subclass of Sinatra::Base will be # used and stored. # # (Rack::Lint validates your application and the requests and # responses according to the Rack spec.) # # @return [Sinatra::Base] def app @app ||= Class.new Sinatra::Base Rack::Lint.new @app end unless method_defined? :options # Processes an OPTIONS request in the context of the current session. # # @param uri [String] # @param params [Hash] # @param env [Hash] def options(uri, params = {}, env = {}, &block) env = env_for(uri, env.merge(:method => "OPTIONS", :params => params)) current_session.send(:process_request, uri, env, &block) end end unless method_defined? :patch # Processes a PATCH request in the context of the current session. # # @param uri [String] # @param params [Hash] # @param env [Hash] def patch(uri, params = {}, env = {}, &block) env = env_for(uri, env.merge(:method => "PATCH", :params => params)) current_session.send(:process_request, uri, env, &block) end end # @return [Boolean] def last_request? last_request true rescue Rack::Test::Error false end # @raise [Rack::Test:Error] If sessions are not enabled for app # @return [Hash] Session of last request, or the empty Hash def session return {} unless last_request? raise Rack::Test::Error, "session not enabled for app" unless last_env["rack.session"] or app.session? last_request.session end # @return The env of the last request def last_env last_request.env end def build_rack_test_session(name) # :nodoc: Session.new rack_mock_session(name) end end end sinatra-2.0.8.1/sinatra-contrib/lib/sinatra/webdav.rb000066400000000000000000000043471360317524000224350ustar00rootroot00000000000000require 'sinatra/base' module Sinatra # = Sinatra::WebDAV # # This extensions provides WebDAV verbs, as defined by RFC 4918 # (https://tools.ietf.org/html/rfc4918). To use this in your app, # just +register+ it: # # require 'sinatra/base' # require 'sinatra/webdav' # # class Application < Sinatra::Base # register Sinatra::WebDAV # # # Now you can use any WebDAV verb: # propfind '/2014/january/21' do # 'I have a lunch at 9 PM' # end # end # # You can use it in classic application just by requring the extension: # # require 'sinatra' # require 'sinatra/webdav' # # mkcol '/2015' do # 'You started 2015!' # end # module WebDAV def self.registered(_) Sinatra::Request.include WebDAV::Request end module Request def self.included(base) base.class_eval do alias _safe? safe? alias _idempotent? idempotent? def safe? _safe? or propfind? end def idempotent? _idempotent? or propfind? or move? or unlock? # or lock? end end end def propfind? request_method == 'PROPFIND' end def proppatch? request_method == 'PROPPATCH' end def mkcol? request_method == 'MKCOL' end def copy? request_method == 'COPY' end def move? request_method == 'MOVE' end #def lock? # request_method == 'LOCK' #end def unlock? request_method == 'UNLOCK' end end def propfind(path, opts = {}, &bk) route 'PROPFIND', path, opts, &bk end def proppatch(path, opts = {}, &bk) route 'PROPPATCH', path, opts, &bk end def mkcol(path, opts = {}, &bk) route 'MKCOL', path, opts, &bk end def copy(path, opts = {}, &bk) route 'COPY', path, opts, &bk end def move(path, opts = {}, &bk) route 'MOVE', path, opts, &bk end #def lock(path, opts = {}, &bk) route 'LOCK', path, opts, &bk end def unlock(path, opts = {}, &bk) route 'UNLOCK', path, opts, &bk end end register WebDAV Delegator.delegate :propfind, :proppatch, :mkcol, :copy, :move, :unlock # :lock end sinatra-2.0.8.1/sinatra-contrib/sinatra-contrib.gemspec000066400000000000000000000043301360317524000230650ustar00rootroot00000000000000# -*- encoding: utf-8 -*- version = File.read(File.expand_path("../../VERSION", __FILE__)).strip Gem::Specification.new do |s| s.name = "sinatra-contrib" s.version = version s.description = "Collection of useful Sinatra extensions" s.homepage = "http://sinatrarb.com/contrib/" s.license = "MIT" s.summary = s.description s.authors = ["https://github.com/sinatra/sinatra/graphs/contributors"] s.email = "sinatrarb@googlegroups.com" s.files = Dir["lib/**/*.rb"] + [ "LICENSE", "README.md", "Rakefile", "ideas.md", "sinatra-contrib.gemspec" ] if s.respond_to?(:metadata) s.metadata = { 'source_code_uri' => 'https://github.com/sinatra/sinatra/tree/master/sinatra-contrib', 'homepage_uri' => 'http://sinatrarb.com/contrib/', 'documentation_uri' => 'https://www.rubydoc.info/gems/sinatra-contrib' } else raise <<-EOF RubyGems 2.0 or newer is required to protect against public gem pushes. You can update your rubygems version by running: gem install rubygems-update update_rubygems: gem update --system EOF end s.required_ruby_version = '>= 2.2.0' s.add_dependency "sinatra", version s.add_dependency "mustermann", "~> 1.0" s.add_dependency "backports", ">= 2.8.2" s.add_dependency "tilt", "~> 2.0" s.add_dependency "rack-protection", version s.add_dependency "multi_json" s.add_development_dependency "rspec", "~> 3.4" s.add_development_dependency "haml" s.add_development_dependency "erubi" s.add_development_dependency "erubis" s.add_development_dependency "slim" s.add_development_dependency "less" s.add_development_dependency "sass" s.add_development_dependency "builder" s.add_development_dependency "liquid" s.add_development_dependency "redcarpet" s.add_development_dependency "RedCloth", "~> 4.2.9" s.add_development_dependency "asciidoctor" s.add_development_dependency "radius" s.add_development_dependency "coffee-script" s.add_development_dependency "nokogiri" s.add_development_dependency "creole" s.add_development_dependency "wikicloth" s.add_development_dependency "markaby" s.add_development_dependency "rake", "< 11" s.add_development_dependency "rack-test" end sinatra-2.0.8.1/sinatra-contrib/spec/000077500000000000000000000000001360317524000173535ustar00rootroot00000000000000sinatra-2.0.8.1/sinatra-contrib/spec/capture_spec.rb000066400000000000000000000044371360317524000223650ustar00rootroot00000000000000# -*- coding: utf-8 -*- require 'slim' require 'spec_helper' describe Sinatra::Capture do subject do Sinatra.new do enable :inline_templates helpers Sinatra::Capture end.new! end Tilt.prefer Tilt::ERBTemplate extend Forwardable def_delegators :subject, :capture, :capture_later def render(engine, template) subject.send(:render, engine, template.to_sym).strip.gsub(/\s+/, ' ') end shared_examples_for "a template language" do |engine| lang = engine if engine == :erubi || engine == :erubis lang = :erb end if engine == :hamlit lang = :haml end require "#{engine}" it "captures content" do expect(render(engine, "simple_#{lang}")).to eq("Say Hello World!") end it "allows nested captures" do expect(render(engine, "nested_#{lang}")).to eq("Say Hello World!") end end describe('haml') { it_behaves_like "a template language", :haml } describe('hamlit') { it_behaves_like "a template language", :hamlit } describe('slim') { it_behaves_like "a template language", :slim } describe('erubi') { it_behaves_like "a template language", :erubi } describe('erubis') { it_behaves_like "a template language", :erubis } describe 'erb' do it_behaves_like "a template language", :erb it "handles utf-8 encoding" do expect(render(:erb, "utf_8")).to eq("UTF-8 –") end it "handles ISO-8859-1 encoding" do expect(render(:erb, "iso_8859_1")).to eq("ISO-8859-1 -") end end describe 'without templates' do it 'captures empty blocks' do expect(capture {}).to be_nil end end end __END__ @@ simple_erb Say <% a = capture do %>World<% end %> Hello <%= a %>! @@ nested_erb Say <% a = capture do %> <% b = capture do %>World<% end %> <%= b %>! <% end %> Hello <%= a.strip %> @@ simple_slim | Say - a = capture do | World | Hello #{a.strip}! @@ nested_slim | Say - a = capture do - b = capture do | World | #{b.strip}! | Hello #{a.strip} @@ simple_haml Say - a = capture do World Hello #{a.strip}! @@ nested_haml Say - a = capture do - b = capture do World #{b.strip}! Hello #{a.strip} @@ utf_8 <% a = capture do %>–<% end %> UTF-8 <%= a %> @@ iso_8859_1 <% a = capture do %>-<% end %> ISO-8859-1 <%= a.force_encoding("iso-8859-1") %> sinatra-2.0.8.1/sinatra-contrib/spec/config_file/000077500000000000000000000000001360317524000216175ustar00rootroot00000000000000sinatra-2.0.8.1/sinatra-contrib/spec/config_file/config.txt000066400000000000000000000000001360317524000236130ustar00rootroot00000000000000sinatra-2.0.8.1/sinatra-contrib/spec/config_file/key_value.yaml000066400000000000000000000001031360317524000244610ustar00rootroot00000000000000--- foo: bar bar: <%= "bar" %> something: 42 nested: a: 1 b: 2 sinatra-2.0.8.1/sinatra-contrib/spec/config_file/key_value.yml000066400000000000000000000001031360317524000243200ustar00rootroot00000000000000--- foo: bar bar: <%= "bar" %> something: 42 nested: a: 1 b: 2 sinatra-2.0.8.1/sinatra-contrib/spec/config_file/key_value.yml.erb000066400000000000000000000001171360317524000250740ustar00rootroot00000000000000--- foo: <%= "bar" %> something: <%= 42 %> nested: a: <%= 1 %> b: <%= 2 %> sinatra-2.0.8.1/sinatra-contrib/spec/config_file/key_value_override.yml000066400000000000000000000000151360317524000262210ustar00rootroot00000000000000--- foo: foo sinatra-2.0.8.1/sinatra-contrib/spec/config_file/missing_env.yml000066400000000000000000000000531360317524000246610ustar00rootroot00000000000000--- foo: production: 10 development: 20sinatra-2.0.8.1/sinatra-contrib/spec/config_file/with_env_defaults.yml000066400000000000000000000002601360317524000260520ustar00rootroot00000000000000--- default: &default foo: default bar: baz development: <<: *default foo: development production: <<: *default foo: production test: <<: *default foo: test sinatra-2.0.8.1/sinatra-contrib/spec/config_file/with_envs.yml000066400000000000000000000001241360317524000243450ustar00rootroot00000000000000--- development: foo: development production: foo: production test: foo: test sinatra-2.0.8.1/sinatra-contrib/spec/config_file/with_nested_envs.yml000066400000000000000000000003101360317524000257040ustar00rootroot00000000000000--- database: production: adapter: postgresql database: foo_production development: adapter: sqlite database: db/development.db test: adapter: sqlite database: db/test.dbsinatra-2.0.8.1/sinatra-contrib/spec/config_file_spec.rb000066400000000000000000000064271360317524000231670ustar00rootroot00000000000000require 'spec_helper' describe Sinatra::ConfigFile do def config_file(*args, &block) mock_app do register Sinatra::ConfigFile set :root, File.expand_path('../config_file', __FILE__) instance_eval(&block) if block config_file(*args) end end it 'should set options from a simple config_file' do config_file 'key_value.yml' expect(settings.foo).to eq('bar') expect(settings.something).to eq(42) end it 'should create indifferent hashes' do config_file 'key_value.yml' expect(settings.nested['a']).to eq(1) expect(settings.nested[:a]).to eq(1) end it 'should render options in ERB tags when using .yml files' do config_file 'key_value.yml' expect(settings.bar).to eq "bar" expect(settings.something).to eq 42 expect(settings.nested['a']).to eq 1 expect(settings.nested[:a]).to eq 1 expect(settings.nested['b']).to eq 2 expect(settings.nested[:b]).to eq 2 end it 'should render options in ERB tags when using .yml.erb files' do config_file 'key_value.yml.erb' expect(settings.foo).to eq("bar") expect(settings.something).to eq(42) expect(settings.nested['a']).to eq(1) expect(settings.nested[:a]).to eq(1) expect(settings.nested['b']).to eq(2) expect(settings.nested[:b]).to eq(2) end it 'should render options in ERB tags when using .yaml files' do config_file 'key_value.yaml' expect(settings.foo).to eq("bar") expect(settings.something).to eq(42) expect(settings.nested['a']).to eq(1) expect(settings.nested[:a]).to eq(1) expect(settings.nested['b']).to eq(2) expect(settings.nested[:b]).to eq(2) end it 'should raise error if config file extension is not .yml, .yaml or .erb' do expect{ config_file 'config.txt' }.to raise_error(Sinatra::ConfigFile::UnsupportedConfigType) end it 'should recognize env specific settings per file' do config_file 'with_envs.yml' expect(settings.foo).to eq('test') end it 'should recognize env specific settings per setting' do config_file 'with_nested_envs.yml' expect(settings.database[:adapter]).to eq('sqlite') end it 'should not set present values to nil if the current env is missing' do # first let's check the test is actually working properly config_file('missing_env.yml') { set :foo => 42, :environment => :production } expect(settings.foo).to eq(10) # now test it config_file('missing_env.yml') { set :foo => 42, :environment => :test } expect(settings.foo).to eq(42) end it 'should prioritize settings in latter files' do # first let's check the test is actually working properly config_file 'key_value.yml' expect(settings.foo).to eq('bar') # now test it config_file 'key_value_override.yml' expect(settings.foo).to eq('foo') end context 'when file contains superfluous environments' do before { config_file 'with_env_defaults.yml' } it 'loads settings for the current environment anyway' do expect { settings.foo }.not_to raise_error end end context 'when file contains defaults' do before { config_file 'with_env_defaults.yml' } it 'uses the overridden value' do expect(settings.foo).to eq('test') end it 'uses the default value' do expect(settings.bar).to eq('baz') end end end sinatra-2.0.8.1/sinatra-contrib/spec/content_for/000077500000000000000000000000001360317524000216735ustar00rootroot00000000000000sinatra-2.0.8.1/sinatra-contrib/spec/content_for/different_key.erb000066400000000000000000000000461360317524000252030ustar00rootroot00000000000000<% content_for :bar do %>bar<% end %> sinatra-2.0.8.1/sinatra-contrib/spec/content_for/different_key.erubis000066400000000000000000000000461360317524000257240ustar00rootroot00000000000000<% content_for :bar do %>bar<% end %> sinatra-2.0.8.1/sinatra-contrib/spec/content_for/different_key.haml000066400000000000000000000000341360317524000253510ustar00rootroot00000000000000- content_for :bar do bar sinatra-2.0.8.1/sinatra-contrib/spec/content_for/different_key.hamlit000066400000000000000000000000341360317524000257060ustar00rootroot00000000000000- content_for :bar do bar sinatra-2.0.8.1/sinatra-contrib/spec/content_for/different_key.slim000066400000000000000000000000361360317524000253760ustar00rootroot00000000000000- content_for :bar do | bar sinatra-2.0.8.1/sinatra-contrib/spec/content_for/footer.erb000066400000000000000000000001001360317524000236520ustar00rootroot00000000000000<% if content_for? :foo %> <%= yield_content :foo %> <% end %>sinatra-2.0.8.1/sinatra-contrib/spec/content_for/footer.erubis000066400000000000000000000001001360317524000243730ustar00rootroot00000000000000<% if content_for? :foo %> <%= yield_content :foo %> <% end %>sinatra-2.0.8.1/sinatra-contrib/spec/content_for/footer.haml000066400000000000000000000000551360317524000240340ustar00rootroot00000000000000- if content_for? :foo = yield_content :foosinatra-2.0.8.1/sinatra-contrib/spec/content_for/footer.hamlit000066400000000000000000000000571360317524000243730ustar00rootroot00000000000000- if content_for? :foo != yield_content :foo sinatra-2.0.8.1/sinatra-contrib/spec/content_for/footer.slim000066400000000000000000000000551360317524000240570ustar00rootroot00000000000000- if content_for? :foo = yield_content :foosinatra-2.0.8.1/sinatra-contrib/spec/content_for/layout.erb000066400000000000000000000000321360317524000236750ustar00rootroot00000000000000<%= yield_content :foo %> sinatra-2.0.8.1/sinatra-contrib/spec/content_for/layout.erubis000066400000000000000000000000321360317524000244160ustar00rootroot00000000000000<%= yield_content :foo %> sinatra-2.0.8.1/sinatra-contrib/spec/content_for/layout.haml000066400000000000000000000000251360317524000240500ustar00rootroot00000000000000= yield_content :foo sinatra-2.0.8.1/sinatra-contrib/spec/content_for/layout.hamlit000066400000000000000000000000251360317524000244050ustar00rootroot00000000000000= yield_content :foo sinatra-2.0.8.1/sinatra-contrib/spec/content_for/layout.slim000066400000000000000000000000251360317524000240730ustar00rootroot00000000000000= yield_content :foo sinatra-2.0.8.1/sinatra-contrib/spec/content_for/multiple_blocks.erb000066400000000000000000000002441360317524000255550ustar00rootroot00000000000000<% content_for :foo do %>foo<% end %> <% content_for :foo do %>bar<% end %> <% content_for :baz do %>WON'T RENDER ME<% end %> <% content_for :foo do %>baz<% end %> sinatra-2.0.8.1/sinatra-contrib/spec/content_for/multiple_blocks.erubis000066400000000000000000000002441360317524000262760ustar00rootroot00000000000000<% content_for :foo do %>foo<% end %> <% content_for :foo do %>bar<% end %> <% content_for :baz do %>WON'T RENDER ME<% end %> <% content_for :foo do %>baz<% end %> sinatra-2.0.8.1/sinatra-contrib/spec/content_for/multiple_blocks.haml000066400000000000000000000001741360317524000257300ustar00rootroot00000000000000- content_for :foo do foo - content_for :foo do bar - content_for :baz do WON'T RENDER ME - content_for :foo do baz sinatra-2.0.8.1/sinatra-contrib/spec/content_for/multiple_blocks.hamlit000066400000000000000000000001741360317524000262650ustar00rootroot00000000000000- content_for :foo do foo - content_for :foo do bar - content_for :baz do WON'T RENDER ME - content_for :foo do baz sinatra-2.0.8.1/sinatra-contrib/spec/content_for/multiple_blocks.slim000066400000000000000000000002041360317524000257450ustar00rootroot00000000000000- content_for :foo do | foo - content_for :foo do | bar - content_for :baz do | WON'T RENDER ME - content_for :foo do | baz sinatra-2.0.8.1/sinatra-contrib/spec/content_for/multiple_yields.erb000066400000000000000000000001161360317524000255670ustar00rootroot00000000000000<%= yield_content :foo %> <%= yield_content :foo %> <%= yield_content :foo %> sinatra-2.0.8.1/sinatra-contrib/spec/content_for/multiple_yields.erubis000066400000000000000000000001161360317524000263100ustar00rootroot00000000000000<%= yield_content :foo %> <%= yield_content :foo %> <%= yield_content :foo %> sinatra-2.0.8.1/sinatra-contrib/spec/content_for/multiple_yields.haml000066400000000000000000000000771360317524000257460ustar00rootroot00000000000000= yield_content :foo = yield_content :foo = yield_content :foo sinatra-2.0.8.1/sinatra-contrib/spec/content_for/multiple_yields.hamlit000066400000000000000000000000771360317524000263030ustar00rootroot00000000000000= yield_content :foo = yield_content :foo = yield_content :foo sinatra-2.0.8.1/sinatra-contrib/spec/content_for/multiple_yields.slim000066400000000000000000000000771360317524000257710ustar00rootroot00000000000000= yield_content :foo = yield_content :foo = yield_content :foo sinatra-2.0.8.1/sinatra-contrib/spec/content_for/parameter_value.erb000066400000000000000000000000361360317524000255400ustar00rootroot00000000000000<% content_for :foo, 'foo' %> sinatra-2.0.8.1/sinatra-contrib/spec/content_for/parameter_value.erubis000066400000000000000000000000361360317524000262610ustar00rootroot00000000000000<% content_for :foo, 'foo' %> sinatra-2.0.8.1/sinatra-contrib/spec/content_for/parameter_value.haml000066400000000000000000000000321360317524000257050ustar00rootroot00000000000000- content_for :foo, 'foo' sinatra-2.0.8.1/sinatra-contrib/spec/content_for/parameter_value.hamlit000066400000000000000000000000321360317524000262420ustar00rootroot00000000000000- content_for :foo, 'foo' sinatra-2.0.8.1/sinatra-contrib/spec/content_for/parameter_value.slim000066400000000000000000000000321360317524000257300ustar00rootroot00000000000000- content_for :foo, 'foo' sinatra-2.0.8.1/sinatra-contrib/spec/content_for/passes_values.erb000066400000000000000000000000371360317524000252420ustar00rootroot00000000000000<%= yield_content :foo, 1, 2 %>sinatra-2.0.8.1/sinatra-contrib/spec/content_for/passes_values.erubis000066400000000000000000000000371360317524000257630ustar00rootroot00000000000000<%= yield_content :foo, 1, 2 %>sinatra-2.0.8.1/sinatra-contrib/spec/content_for/passes_values.haml000066400000000000000000000000331360317524000254070ustar00rootroot00000000000000= yield_content :foo, 1, 2 sinatra-2.0.8.1/sinatra-contrib/spec/content_for/passes_values.hamlit000066400000000000000000000000341360317524000257450ustar00rootroot00000000000000!= yield_content :foo, 1, 2 sinatra-2.0.8.1/sinatra-contrib/spec/content_for/passes_values.slim000066400000000000000000000000341360317524000254330ustar00rootroot00000000000000== yield_content :foo, 1, 2 sinatra-2.0.8.1/sinatra-contrib/spec/content_for/same_key.erb000066400000000000000000000000461360317524000241620ustar00rootroot00000000000000<% content_for :foo do %>foo<% end %> sinatra-2.0.8.1/sinatra-contrib/spec/content_for/same_key.erubis000066400000000000000000000000461360317524000247030ustar00rootroot00000000000000<% content_for :foo do %>foo<% end %> sinatra-2.0.8.1/sinatra-contrib/spec/content_for/same_key.haml000066400000000000000000000000341360317524000243300ustar00rootroot00000000000000- content_for :foo do foo sinatra-2.0.8.1/sinatra-contrib/spec/content_for/same_key.hamlit000066400000000000000000000000341360317524000246650ustar00rootroot00000000000000- content_for :foo do foo sinatra-2.0.8.1/sinatra-contrib/spec/content_for/same_key.slim000066400000000000000000000000361360317524000243550ustar00rootroot00000000000000- content_for :foo do | foo sinatra-2.0.8.1/sinatra-contrib/spec/content_for/takes_values.erb000066400000000000000000000001011360317524000250430ustar00rootroot00000000000000<% content_for :foo do |a, b| %><%= a %> <%= b %><% end %>sinatra-2.0.8.1/sinatra-contrib/spec/content_for/takes_values.erubis000066400000000000000000000001011360317524000255640ustar00rootroot00000000000000<% content_for :foo do |a, b| %><%= a %> <%= b %><% end %>sinatra-2.0.8.1/sinatra-contrib/spec/content_for/takes_values.haml000066400000000000000000000000521360317524000252210ustar00rootroot00000000000000- content_for :foo do |a, b| %i= a =b sinatra-2.0.8.1/sinatra-contrib/spec/content_for/takes_values.hamlit000066400000000000000000000000521360317524000255560ustar00rootroot00000000000000- content_for :foo do |a, b| %i= a =b sinatra-2.0.8.1/sinatra-contrib/spec/content_for/takes_values.slim000066400000000000000000000000521360317524000252440ustar00rootroot00000000000000- content_for :foo do |a, b| i= a = b sinatra-2.0.8.1/sinatra-contrib/spec/content_for/yield_block.erb000066400000000000000000000000501360317524000246400ustar00rootroot00000000000000<% yield_content :foo do %>baz<% end %> sinatra-2.0.8.1/sinatra-contrib/spec/content_for/yield_block.erubis000066400000000000000000000000501360317524000253610ustar00rootroot00000000000000<% yield_content :foo do %>baz<% end %> sinatra-2.0.8.1/sinatra-contrib/spec/content_for/yield_block.haml000066400000000000000000000000371360317524000250160ustar00rootroot00000000000000= yield_content :foo do baz sinatra-2.0.8.1/sinatra-contrib/spec/content_for/yield_block.hamlit000066400000000000000000000000401360317524000253450ustar00rootroot00000000000000!= yield_content :foo do baz sinatra-2.0.8.1/sinatra-contrib/spec/content_for/yield_block.slim000066400000000000000000000000411360317524000250340ustar00rootroot00000000000000= yield_content :foo do | baz sinatra-2.0.8.1/sinatra-contrib/spec/content_for_spec.rb000066400000000000000000000215131360317524000232340ustar00rootroot00000000000000require 'spec_helper' describe Sinatra::ContentFor do subject do Sinatra.new do helpers Sinatra::ContentFor set :views, File.expand_path("../content_for", __FILE__) end.new! end Tilt.prefer Tilt::ERBTemplate require 'hamlit/block' Tilt.register Tilt::HamlTemplate, :haml extend Forwardable def_delegators :subject, :content_for, :clear_content_for, :yield_content def render(engine, template) subject.send(:render, engine, template, :layout => false).gsub(/\s/, '') end describe "without templates" do it 'renders blocks declared with the same key you use when rendering' do content_for(:foo) { "foo" } expect(yield_content(:foo)).to eq("foo") end it 'renders blocks more than once' do content_for(:foo) { "foo" } 3.times { expect(yield_content(:foo)).to eq("foo") } end it 'does not render a block with a different key' do content_for(:bar) { "bar" } expect(yield_content(:foo)).to be_empty end it 'renders default content if no block matches the key and a default block is specified' do expect(yield_content(:foo) {}).to be_nil expect(yield_content(:foo) { "foo" }).to eq("foo") end it 'renders multiple blocks with the same key' do content_for(:foo) { "foo" } content_for(:foo) { "bar" } content_for(:bar) { "WON'T RENDER ME" } content_for(:foo) { "baz" } expect(yield_content(:foo)).to eq("foobarbaz") end it 'renders multiple blocks more than once' do content_for(:foo) { "foo" } content_for(:foo) { "bar" } content_for(:bar) { "WON'T RENDER ME" } content_for(:foo) { "baz" } 3.times { expect(yield_content(:foo)).to eq("foobarbaz") } end it 'passes values to the blocks' do content_for(:foo) { |a| a.upcase } expect(yield_content(:foo, 'a')).to eq("A") expect(yield_content(:foo, 'b')).to eq("B") end it 'clears named blocks with the specified key' do content_for(:foo) { "foo" } expect(yield_content(:foo)).to eq("foo") clear_content_for(:foo) expect(yield_content(:foo)).to be_empty end it 'takes an immediate value instead of a block' do content_for(:foo, "foo") expect(yield_content(:foo)).to eq("foo") end context 'when flush option was disabled' do it 'append content' do content_for(:foo, "foo") content_for(:foo, "bar") expect(yield_content(:foo)).to eq("foobar") end end context 'when flush option was enabled' do it 'flush first content' do content_for(:foo, "foo") content_for(:foo, "bar", flush: true) expect(yield_content(:foo)).to eq("bar") end end end # TODO: liquid radius markaby builder nokogiri engines = %w[erb erubi erubis haml hamlit slim] engines.each do |inner| describe inner.capitalize do before :all do begin require inner rescue LoadError => e skip "Skipping: " << e.message end end describe "with yield_content in Ruby" do it 'renders blocks declared with the same key you use when rendering' do render inner, :same_key expect(yield_content(:foo).strip).to eq("foo") end it 'renders blocks more than once' do render inner, :same_key 3.times { expect(yield_content(:foo).strip).to eq("foo") } end it 'does not render a block with a different key' do render inner, :different_key expect(yield_content(:foo)).to be_empty end it 'renders default content if no block matches the key and a default block is specified' do render inner, :different_key expect(yield_content(:foo) { "foo" }).to eq("foo") end it 'renders multiple blocks with the same key' do render inner, :multiple_blocks expect(yield_content(:foo).gsub(/\s/, '')).to eq("foobarbaz") end it 'renders multiple blocks more than once' do render inner, :multiple_blocks 3.times { expect(yield_content(:foo).gsub(/\s/, '')).to eq("foobarbaz") } end it 'passes values to the blocks' do render inner, :takes_values expect(yield_content(:foo, 1, 2).gsub(/\s/, '')).to eq("12") end end describe "with content_for in Ruby" do it 'renders blocks declared with the same key you use when rendering' do content_for(:foo) { "foo" } expect(render(inner, :layout)).to eq("foo") end it 'renders blocks more than once' do content_for(:foo) { "foo" } expect(render(inner, :multiple_yields)).to eq("foofoofoo") end it 'does not render a block with a different key' do content_for(:bar) { "foo" } expect(render(inner, :layout)).to be_empty end it 'renders multiple blocks with the same key' do content_for(:foo) { "foo" } content_for(:foo) { "bar" } content_for(:bar) { "WON'T RENDER ME" } content_for(:foo) { "baz" } expect(render(inner, :layout)).to eq("foobarbaz") end it 'renders multiple blocks more than once' do content_for(:foo) { "foo" } content_for(:foo) { "bar" } content_for(:bar) { "WON'T RENDER ME" } content_for(:foo) { "baz" } expect(render(inner, :multiple_yields)).to eq("foobarbazfoobarbazfoobarbaz") end it 'passes values to the blocks' do content_for(:foo) { |a,b| "#{a}#{b}" } expect(render(inner, :passes_values)).to eq("12") end it 'clears named blocks with the specified key' do content_for(:foo) { "foo" } expect(render(inner, :layout)).to eq("foo") clear_content_for(:foo) expect(render(inner, :layout)).to be_empty end end describe "with content_for? in Ruby" do it 'renders block if key is set' do content_for(:foo) { "foot" } expect(render(inner, :footer)).to eq("foot") end it 'does not render a block if different key' do content_for(:different_key) { "foot" } expect(render(inner, :footer)).to be_empty end end engines.each do |outer| describe "with yield_content in #{outer.capitalize}" do def body last_response.body.gsub(/\s/, '') end before :all do begin require outer rescue LoadError => e skip "Skipping: " << e.message end end before do mock_app do helpers Sinatra::ContentFor set inner, :layout_engine => outer set :views, File.expand_path("../content_for", __FILE__) get('/:view') { render(inner, params[:view].to_sym) } get('/:layout/:view') do render inner, params[:view].to_sym, :layout => params[:layout].to_sym end end end describe 'with a default content block' do describe 'when content_for key exists' do it 'ignores default content and renders content' do expect(get('/yield_block/same_key')).to be_ok expect(body).to eq("foo") end end describe 'when content_for key is missing' do it 'renders default content block' do expect(get('/yield_block/different_key')).to be_ok expect(body).to eq("baz") end end end it 'renders content set as parameter' do expect(get('/parameter_value')).to be_ok expect(body).to eq("foo") end it 'renders blocks declared with the same key you use when rendering' do expect(get('/same_key')).to be_ok expect(body).to eq("foo") end it 'renders blocks more than once' do expect(get('/multiple_yields/same_key')).to be_ok expect(body).to eq("foofoofoo") end it 'does not render a block with a different key' do expect(get('/different_key')).to be_ok expect(body).to be_empty end it 'renders multiple blocks with the same key' do expect(get('/multiple_blocks')).to be_ok expect(body).to eq("foobarbaz") end it 'renders multiple blocks more than once' do expect(get('/multiple_yields/multiple_blocks')).to be_ok expect(body).to eq("foobarbazfoobarbazfoobarbaz") end it 'passes values to the blocks' do expect(get('/passes_values/takes_values')).to be_ok expect(body).to eq("12") end end end end end end sinatra-2.0.8.1/sinatra-contrib/spec/cookies_spec.rb000066400000000000000000000474721360317524000223640ustar00rootroot00000000000000require 'spec_helper' describe Sinatra::Cookies do def cookie_route(*cookies, &block) result = nil set_cookie(cookies) @cookie_app.get('/') do result = instance_eval(&block) "ok" end get '/', {}, @headers || {} expect(last_response).to be_ok expect(body).to eq("ok") result end def cookies(*set_cookies) cookie_route(*set_cookies) { cookies } end before do app = nil mock_app do helpers Sinatra::Cookies app = self end @cookie_app = app clear_cookies end describe :cookie_route do it 'runs the block' do ran = false cookie_route { ran = true } expect(ran).to be true end it 'returns the block result' do expect(cookie_route { 42 }).to eq(42) end end describe :== do it 'is comparable to hashes' do expect(cookies).to eq({}) end it 'is comparable to anything that responds to to_hash' do other = Struct.new(:to_hash).new({}) expect(cookies).to eq(other) end end describe :[] do it 'allows access to request cookies' do expect(cookies("foo=bar")["foo"]).to eq("bar") end it 'takes symbols as keys' do expect(cookies("foo=bar")[:foo]).to eq("bar") end it 'returns nil for missing keys' do expect(cookies("foo=bar")['bar']).to be_nil end it 'allows access to response cookies' do expect(cookie_route do response.set_cookie 'foo', 'bar' cookies['foo'] end).to eq('bar') end it 'favors response cookies over request cookies' do expect(cookie_route('foo=bar') do response.set_cookie 'foo', 'baz' cookies['foo'] end).to eq('baz') end it 'takes the last value for response cookies' do expect(cookie_route do response.set_cookie 'foo', 'bar' response.set_cookie 'foo', 'baz' cookies['foo'] end).to eq('baz') end end describe :[]= do it 'sets cookies to httponly' do expect(cookie_route do cookies['foo'] = 'bar' response['Set-Cookie'].lines.detect { |l| l.start_with? 'foo=' } end).to include('HttpOnly') end it 'sets domain to nil if localhost' do @headers = {'HTTP_HOST' => 'localhost'} expect(cookie_route do cookies['foo'] = 'bar' response['Set-Cookie'] end).not_to include("domain") end it 'sets the domain' do expect(cookie_route do cookies['foo'] = 'bar' response['Set-Cookie'].lines.detect { |l| l.start_with? 'foo=' } end).to include('domain=example.org') end it 'sets path to / by default' do expect(cookie_route do cookies['foo'] = 'bar' response['Set-Cookie'].lines.detect { |l| l.start_with? 'foo=' } end).to include('path=/') end it 'sets path to the script_name if app is nested' do expect(cookie_route do request.script_name = '/foo' cookies['foo'] = 'bar' response['Set-Cookie'].lines.detect { |l| l.start_with? 'foo=' } end).to include('path=/foo') end it 'sets a cookie' do cookie_route { cookies['foo'] = 'bar' } expect(cookie_jar['foo']).to eq('bar') end it 'adds a value to the cookies hash' do expect(cookie_route do cookies['foo'] = 'bar' cookies['foo'] end).to eq('bar') end end describe :assoc do it 'behaves like Hash#assoc' do cookies('foo=bar').assoc('foo') == ['foo', 'bar'] end end if Hash.method_defined? :assoc describe :clear do it 'removes request cookies from cookies hash' do jar = cookies('foo=bar') expect(jar['foo']).to eq('bar') jar.clear expect(jar['foo']).to be_nil end it 'removes response cookies from cookies hash' do expect(cookie_route do cookies['foo'] = 'bar' cookies.clear cookies['foo'] end).to be_nil end it 'expires existing cookies' do expect(cookie_route("foo=bar") do cookies.clear response['Set-Cookie'] end).to include("foo=;", "expires=", "1970 00:00:00") end end describe :compare_by_identity? do it { expect(cookies).not_to be_compare_by_identity } end describe :default do it { expect(cookies.default).to be_nil } end describe :default_proc do it { expect(cookies.default_proc).to be_nil } end describe :delete do it 'removes request cookies from cookies hash' do jar = cookies('foo=bar') expect(jar['foo']).to eq('bar') jar.delete 'foo' expect(jar['foo']).to be_nil end it 'removes response cookies from cookies hash' do expect(cookie_route do cookies['foo'] = 'bar' cookies.delete 'foo' cookies['foo'] end).to be_nil end it 'expires existing cookies' do expect(cookie_route("foo=bar") do cookies.delete 'foo' response['Set-Cookie'] end).to include("foo=;", "expires=", "1970 00:00:00") end it 'honours the app cookie_options' do @cookie_app.class_eval do set :cookie_options, { :path => '/foo', :domain => 'bar.com', :secure => true, :httponly => true } end cookie_header = cookie_route("foo=bar") do cookies.delete 'foo' response['Set-Cookie'] end expect(cookie_header).to include("path=/foo;", "domain=bar.com;", "secure;", "HttpOnly") end it 'does not touch other cookies' do expect(cookie_route("foo=bar", "bar=baz") do cookies.delete 'foo' cookies['bar'] end).to eq('baz') end it 'returns the previous value for request cookies' do expect(cookie_route("foo=bar") do cookies.delete "foo" end).to eq("bar") end it 'returns the previous value for response cookies' do expect(cookie_route do cookies['foo'] = 'bar' cookies.delete "foo" end).to eq("bar") end it 'returns nil for non-existing cookies' do expect(cookie_route { cookies.delete("foo") }).to be_nil end end describe :delete_if do it 'deletes cookies that match the block' do expect(cookie_route('foo=bar') do cookies['bar'] = 'baz' cookies['baz'] = 'foo' cookies.delete_if { |*a| a.include? 'bar' } cookies.values_at 'foo', 'bar', 'baz' end).to eq([nil, nil, 'foo']) end end describe :each do it 'loops through cookies' do keys = [] foo = nil bar = nil cookie_route('foo=bar', 'bar=baz') do cookies.each do |key, value| foo = value if key == 'foo' bar = value if key == 'bar' keys << key end end expect(keys.sort).to eq(['bar', 'foo']) expect(foo).to eq('bar') expect(bar).to eq('baz') end it 'favors response over request cookies' do seen = false key = nil value = nil cookie_route('foo=bar') do cookies[:foo] = 'baz' cookies.each do |k,v| key = k value = v end end expect(key).to eq('foo') expect(value).to eq('baz') expect(seen).to eq(false) end it 'does not loop through deleted cookies' do cookie_route('foo=bar') do cookies.delete :foo cookies.each { fail } end end it 'returns an enumerator' do keys = [] cookie_route('foo=bar') do enum = cookies.each enum.each { |key, value| keys << key } end keys.each{ |key| expect(key).to eq('foo')} end end describe :each_key do it 'loops through cookies' do keys = [] cookie_route('foo=bar', 'bar=baz') do cookies.each_key do |key| keys << key end end expect(keys.sort).to eq(['bar', 'foo']) end it 'only yields keys once' do seen = false cookie_route('foo=bar') do cookies[:foo] = 'baz' end expect(seen).to eq(false) end it 'does not loop through deleted cookies' do cookie_route('foo=bar') do cookies.delete :foo cookies.each_key { fail } end end it 'returns an enumerator' do keys = [] cookie_route('foo=bar') do enum = cookies.each_key enum.each { |key| keys << key } end keys.each{ |key| expect(key).to eq('foo')} end end describe :each_pair do it 'loops through cookies' do keys = [] foo = nil bar = nil cookie_route('foo=bar', 'bar=baz') do cookies.each_pair do |key, value| foo = value if key == 'foo' bar = value if key == 'bar' keys << key end end expect(keys.sort).to eq(['bar', 'foo']) expect(foo).to eq('bar') expect(bar).to eq('baz') end it 'favors response over request cookies' do seen = false key = nil value = nil cookie_route('foo=bar') do cookies[:foo] = 'baz' cookies.each_pair do |k, v| key = k value = v end end expect(key).to eq('foo') expect(value).to eq('baz') expect(seen).to eq(false) end it 'does not loop through deleted cookies' do cookie_route('foo=bar') do cookies.delete :foo cookies.each_pair { fail } end end it 'returns an enumerator' do keys = [] cookie_route('foo=bar') do enum = cookies.each_pair enum.each { |key, value| keys << key } end keys.each{ |key| expect(key).to eq('foo')} end end describe :each_value do it 'loops through cookies' do values = [] cookie_route('foo=bar', 'bar=baz') do cookies.each_value do |value| values << value end end expect(values.sort).to eq(['bar', 'baz']) end it 'favors response over request cookies' do value = nil cookie_route('foo=bar') do cookies[:foo] = 'baz' cookies.each_value do |v| value = v end end expect(value).to eq('baz') end it 'does not loop through deleted cookies' do cookie_route('foo=bar') do cookies.delete :foo cookies.each_value { fail } end end it 'returns an enumerator' do enum = nil cookie_route('foo=bar') do enum = cookies.each_value end enum.each { |value| expect(value).to eq('bar') } end end describe :empty? do it 'returns true if there are no cookies' do expect(cookies).to be_empty end it 'returns false if there are request cookies' do expect(cookies('foo=bar')).not_to be_empty end it 'returns false if there are response cookies' do expect(cookie_route do cookies['foo'] = 'bar' cookies.empty? end).to be false end it 'becomes true if response cookies are removed' do expect(cookie_route do cookies['foo'] = 'bar' cookies.delete :foo cookies.empty? end).to be true end it 'becomes true if request cookies are removed' do expect(cookie_route('foo=bar') do cookies.delete :foo cookies.empty? end).to be_truthy end it 'becomes true after clear' do expect(cookie_route('foo=bar', 'bar=baz') do cookies['foo'] = 'bar' cookies.clear cookies.empty? end).to be_truthy end end describe :fetch do it 'returns values from request cookies' do expect(cookies('foo=bar').fetch('foo')).to eq('bar') end it 'returns values from response cookies' do expect(cookie_route do cookies['foo'] = 'bar' cookies.fetch('foo') end).to eq('bar') end it 'favors response over request cookies' do expect(cookie_route('foo=baz') do cookies['foo'] = 'bar' cookies.fetch('foo') end).to eq('bar') end it 'raises an exception if key does not exist' do error = if defined? JRUBY_VERSION IndexError else KeyError end expect { cookies.fetch('foo') }.to raise_exception(error) end it 'returns the block result if missing' do expect(cookies.fetch('foo') { 'bar' }).to eq('bar') end end describe :flatten do it { expect(cookies('foo=bar').flatten).to eq({'foo' => 'bar'}.flatten) } end if Hash.method_defined? :flatten describe :has_key? do it 'checks request cookies' do expect(cookies('foo=bar')).to have_key('foo') end it 'checks response cookies' do jar = cookies jar['foo'] = 'bar' expect(jar).to have_key(:foo) end it 'does not use deleted cookies' do jar = cookies('foo=bar') jar.delete :foo expect(jar).not_to have_key('foo') end end describe :has_value? do it 'checks request cookies' do expect(cookies('foo=bar')).to have_value('bar') end it 'checks response cookies' do jar = cookies jar[:foo] = 'bar' expect(jar).to have_value('bar') end it 'does not use deleted cookies' do jar = cookies('foo=bar') jar.delete :foo expect(jar).not_to have_value('bar') end end describe :include? do it 'checks request cookies' do expect(cookies('foo=bar')).to include('foo') end it 'checks response cookies' do jar = cookies jar['foo'] = 'bar' expect(jar).to include(:foo) end it 'does not use deleted cookies' do jar = cookies('foo=bar') jar.delete :foo expect(jar).not_to include('foo') end end describe :keep_if do it 'removes entries' do jar = cookies('foo=bar', 'bar=baz') jar.keep_if { |*args| args == ['bar', 'baz'] } expect(jar).to eq({'bar' => 'baz'}) end end describe :key do it 'checks request cookies' do expect(cookies('foo=bar').key('bar')).to eq('foo') end it 'checks response cookies' do jar = cookies jar['foo'] = 'bar' expect(jar.key('bar')).to eq('foo') end it 'returns nil when missing' do expect(cookies('foo=bar').key('baz')).to be_nil end end describe :key? do it 'checks request cookies' do expect(cookies('foo=bar').key?('foo')).to be true end it 'checks response cookies' do jar = cookies jar['foo'] = 'bar' expect(jar.key?(:foo)).to be true end it 'does not use deleted cookies' do jar = cookies('foo=bar') jar.delete :foo expect(jar.key?('foo')).to be false end end describe :keys do it { expect(cookies('foo=bar').keys).to eq(['foo']) } end describe :length do it { expect(cookies.length).to eq(0) } it { expect(cookies('foo=bar').length).to eq(1) } end describe :member? do it 'checks request cookies' do expect(cookies('foo=bar').member?('foo')).to be true end it 'checks response cookies' do jar = cookies jar['foo'] = 'bar' expect(jar.member?(:foo)).to be true end it 'does not use deleted cookies' do jar = cookies('foo=bar') jar.delete :foo expect(jar.member?('foo')).to be false end end describe :merge do it 'is mergable with a hash' do expect(cookies('foo=bar').merge(:bar => :baz)).to eq({"foo" => "bar", :bar => :baz}) end it 'does not create cookies' do jar = cookies('foo=bar') jar.merge(:bar => 'baz') expect(jar).not_to include(:bar) end it 'takes a block for conflict resolution' do update = {'foo' => 'baz', 'bar' => 'baz'} merged = cookies('foo=bar').merge(update) do |key, old, other| expect(key).to eq('foo') expect(old).to eq('bar') expect(other).to eq('baz') 'foo' end expect(merged['foo']).to eq('foo') end end describe :merge! do it 'creates cookies' do jar = cookies('foo=bar') jar.merge! :bar => 'baz' expect(jar).to include('bar') end it 'overrides existing values' do jar = cookies('foo=bar') jar.merge! :foo => "baz" expect(jar["foo"]).to eq("baz") end it 'takes a block for conflict resolution' do update = {'foo' => 'baz', 'bar' => 'baz'} jar = cookies('foo=bar') jar.merge!(update) do |key, old, other| expect(key).to eq('foo') expect(old).to eq('bar') expect(other).to eq('baz') 'foo' end expect(jar['foo']).to eq('foo') end end describe :rassoc do it 'behaves like Hash#assoc' do cookies('foo=bar').rassoc('bar') == ['foo', 'bar'] end end if Hash.method_defined? :rassoc describe :reject do it 'removes entries from new hash' do jar = cookies('foo=bar', 'bar=baz') sub = jar.reject { |*args| args == ['bar', 'baz'] } expect(sub).to eq({'foo' => 'bar'}) expect(jar['bar']).to eq('baz') end end describe :reject! do it 'removes entries' do jar = cookies('foo=bar', 'bar=baz') jar.reject! { |*args| args == ['bar', 'baz'] } expect(jar).to eq({'foo' => 'bar'}) end end describe :replace do it 'replaces entries' do jar = cookies('foo=bar', 'bar=baz') jar.replace 'foo' => 'baz', 'baz' => 'bar' expect(jar).to eq({'foo' => 'baz', 'baz' => 'bar'}) end end describe :set do it 'sets a cookie' do cookie_route { cookies.set('foo', value: 'bar') } expect(cookie_jar['foo']).to eq('bar') end it 'sets a cookie with HttpOnly' do expect(cookie_route do request.script_name = '/foo' cookies.set('foo', value: 'bar', httponly: true) response['Set-Cookie'].lines.detect { |l| l.start_with? 'foo=' } end).to include('HttpOnly') end it 'sets a cookie without HttpOnly' do expect(cookie_route do request.script_name = '/foo' cookies.set('foo', value: 'bar', httponly: false) response['Set-Cookie'].lines.detect { |l| l.start_with? 'foo=' } end).not_to include('HttpOnly') end end describe :select do it 'removes entries from new hash' do jar = cookies('foo=bar', 'bar=baz') sub = jar.select { |*args| args != ['bar', 'baz'] } expect(sub).to eq({'foo' => 'bar'}.select { true }) expect(jar['bar']).to eq('baz') end end describe :select! do it 'removes entries' do jar = cookies('foo=bar', 'bar=baz') jar.select! { |*args| args != ['bar', 'baz'] } expect(jar).to eq({'foo' => 'bar'}) end end if Hash.method_defined? :select! describe :shift do it 'removes from the hash' do jar = cookies('foo=bar') expect(jar.shift).to eq(['foo', 'bar']) expect(jar).not_to include('bar') end end describe :size do it { expect(cookies.size).to eq(0) } it { expect(cookies('foo=bar').size).to eq(1) } end describe :update do it 'creates cookies' do jar = cookies('foo=bar') jar.update :bar => 'baz' expect(jar).to include('bar') end it 'overrides existing values' do jar = cookies('foo=bar') jar.update :foo => "baz" expect(jar["foo"]).to eq("baz") end it 'takes a block for conflict resolution' do merge = {'foo' => 'baz', 'bar' => 'baz'} jar = cookies('foo=bar') jar.update(merge) do |key, old, other| expect(key).to eq('foo') expect(old).to eq('bar') expect(other).to eq('baz') 'foo' end expect(jar['foo']).to eq('foo') end end describe :value? do it 'checks request cookies' do expect(cookies('foo=bar').value?('bar')).to be true end it 'checks response cookies' do jar = cookies jar[:foo] = 'bar' expect(jar.value?('bar')).to be true end it 'does not use deleted cookies' do jar = cookies('foo=bar') jar.delete :foo expect(jar.value?('bar')).to be false end end describe :values do it { expect(cookies('foo=bar', 'bar=baz').values.sort).to eq(['bar', 'baz']) } end describe :values_at do it { expect(cookies('foo=bar', 'bar=baz').values_at('foo')).to eq(['bar']) } end end sinatra-2.0.8.1/sinatra-contrib/spec/custom_logger_spec.rb000066400000000000000000000015771360317524000235750ustar00rootroot00000000000000require 'spec_helper' require 'sinatra/custom_logger' describe Sinatra::CustomLogger do before do rack_logger = @rack_logger = double mock_app do helpers Sinatra::CustomLogger before do env['rack.logger'] = rack_logger end get '/' do logger.info 'Logged message' 'Response' end end end describe '#logger' do it 'falls back to request.logger' do expect(@rack_logger).to receive(:info).with('Logged message') get '/' end context 'logger setting is set' do before do custom_logger = @custom_logger = double @app.class_eval do configure do set :logger, custom_logger end end end it 'calls custom logger' do expect(@custom_logger).to receive(:info).with('Logged message') get '/' end end end end sinatra-2.0.8.1/sinatra-contrib/spec/extension_spec.rb000066400000000000000000000012361360317524000227300ustar00rootroot00000000000000require 'spec_helper' describe Sinatra::Extension do module ExampleExtension extend Sinatra::Extension set :foo, :bar settings.set :bar, :blah configure :test, :production do set :reload_stuff, false end configure :development do set :reload_stuff, true end get '/' do "from extension, yay" end end before { mock_app { register ExampleExtension }} it('allows using set') { expect(settings.foo).to eq(:bar) } it('implements configure') { expect(settings.reload_stuff).to be false } it 'allows defing routes' do expect(get('/')).to be_ok expect(body).to eq("from extension, yay") end end sinatra-2.0.8.1/sinatra-contrib/spec/json_spec.rb000066400000000000000000000063471360317524000216750ustar00rootroot00000000000000require 'multi_json' require 'spec_helper' require 'okjson' shared_examples_for "a json encoder" do |lib, const| before do begin require lib if lib @encoder = eval(const) rescue LoadError skip "unable to load #{lib}" end end it "allows setting :encoder to #{const}" do enc = @encoder mock_app { get('/') { json({'foo' => 'bar'}, :encoder => enc) }} results_in 'foo' => 'bar' end it "allows setting settings.json_encoder to #{const}" do enc = @encoder mock_app do set :json_encoder, enc get('/') { json 'foo' => 'bar' } end results_in 'foo' => 'bar' end end describe Sinatra::JSON do def mock_app(&block) super do class_eval(&block) end end def results_in(obj) expect(OkJson.decode(get('/').body)).to eq(obj) end it "encodes objects to json out of the box" do mock_app { get('/') { json :foo => [1, 'bar', nil] } } results_in 'foo' => [1, 'bar', nil] end it "sets the content type to 'application/json'" do mock_app { get('/') { json({}) } } expect(get('/')["Content-Type"]).to include("application/json") end it "allows overriding content type with :content_type" do mock_app { get('/') { json({}, :content_type => "foo/bar") } } expect(get('/')["Content-Type"]).to eq("foo/bar") end it "accepts shorthands for :content_type" do mock_app { get('/') { json({}, :content_type => :js) } } expect(get('/')["Content-Type"]).to eq("application/javascript;charset=utf-8") end it 'calls generate on :encoder if available' do enc = Object.new def enc.generate(obj) obj.inspect end mock_app { get('/') { json(42, :encoder => enc) }} expect(get('/').body).to eq('42') end it 'calls encode on :encoder if available' do enc = Object.new def enc.encode(obj) obj.inspect end mock_app { get('/') { json(42, :encoder => enc) }} expect(get('/').body).to eq('42') end it 'sends :encoder as method call if it is a Symbol' do mock_app { get('/') { json(42, :encoder => :inspect) }} expect(get('/').body).to eq('42') end it 'calls generate on settings.json_encoder if available' do enc = Object.new def enc.generate(obj) obj.inspect end mock_app do set :json_encoder, enc get('/') { json 42 } end expect(get('/').body).to eq('42') end it 'calls encode on settings.json_encode if available' do enc = Object.new def enc.encode(obj) obj.inspect end mock_app do set :json_encoder, enc get('/') { json 42 } end expect(get('/').body).to eq('42') end it 'sends settings.json_encode as method call if it is a Symbol' do mock_app do set :json_encoder, :inspect get('/') { json 42 } end expect(get('/').body).to eq('42') end describe('Yajl') { it_should_behave_like "a json encoder", "yajl", "Yajl::Encoder" } unless defined? JRUBY_VERSION describe('JSON') { it_should_behave_like "a json encoder", "json", "::JSON" } describe('OkJson') { it_should_behave_like "a json encoder", nil, "OkJson" } describe('to_json') { it_should_behave_like "a json encoder", "json", ":to_json" } describe('without') { it_should_behave_like "a json encoder", nil, "Sinatra::JSON" } end sinatra-2.0.8.1/sinatra-contrib/spec/link_header_spec.rb000066400000000000000000000044771360317524000231730ustar00rootroot00000000000000require 'spec_helper' describe Sinatra::LinkHeader do before do mock_app do helpers Sinatra::LinkHeader before('/') { link 'something', :rel => 'from-filter', :foo => :bar } get '/' do link :something, 'booyah' end get '/style' do stylesheet '/style.css' end get '/prefetch' do prefetch '/foo' end get '/link_headers' do response['Link'] = " ;bar=\"baz\"" stylesheet '/style.css' prefetch '/foo' link_headers end end end describe :link do it "sets link headers" do get '/' expect(headers['Link'].lines).to include('; rel="something"') end it "returns link html tags" do get '/' expect(body).to eq('') end it "takes an options hash" do get '/' elements = ["", "foo=\"bar\"", "rel=\"from-filter\""] expect(headers['Link'].split(",\n").first.strip.split('; ').sort).to eq(elements) end end describe :stylesheet do it 'sets link headers' do get '/style' expect(headers['Link']).to match(%r{^;}) end it 'sets type to text/css' do get '/style' expect(headers['Link']).to include('type="text/css"') end it 'sets rel to stylesheet' do get '/style' expect(headers['Link']).to include('rel="stylesheet"') end it 'returns html tag' do get '/style' expect(body).to match(%r{^;}) end it 'sets rel to prefetch' do get '/prefetch' expect(headers['Link']).to include('rel="prefetch"') end it 'returns html tag' do get '/prefetch' expect(body).to eq('') end end describe :link_headers do it 'generates html for all link headers' do get '/link_headers' expect(body).to include('') expect(body).to include('') end end end sinatra-2.0.8.1/sinatra-contrib/spec/multi_route_spec.rb000066400000000000000000000023561360317524000232700ustar00rootroot00000000000000require 'spec_helper' describe Sinatra::MultiRoute do it 'does not break normal routing' do mock_app do register Sinatra::MultiRoute get('/') { 'normal' } end expect(get('/')).to be_ok expect(body).to eq('normal') end it 'supports multiple routes' do mock_app do register Sinatra::MultiRoute get('/foo', '/bar') { 'paths' } end expect(get('/foo')).to be_ok expect(body).to eq('paths') expect(get('/bar')).to be_ok expect(body).to eq('paths') end it 'triggers conditions' do count = 0 mock_app do register Sinatra::MultiRoute set(:some_condition) { |_| count += 1 } get('/foo', '/bar', :some_condition => true) { 'paths' } end expect(count).to eq(4) end it 'supports multiple verbs' do mock_app do register Sinatra::MultiRoute route('PUT', 'POST', '/') { 'verb' } end expect(post('/')).to be_ok expect(body).to eq('verb') expect(put('/')).to be_ok expect(body).to eq('verb') end it 'takes symbols as verbs' do mock_app do register Sinatra::MultiRoute route(:get, '/baz') { 'symbol as verb' } end expect(get('/baz')).to be_ok expect(body).to eq('symbol as verb') end end sinatra-2.0.8.1/sinatra-contrib/spec/namespace/000077500000000000000000000000001360317524000213075ustar00rootroot00000000000000sinatra-2.0.8.1/sinatra-contrib/spec/namespace/foo.erb000066400000000000000000000000031360317524000225550ustar00rootroot00000000000000hi sinatra-2.0.8.1/sinatra-contrib/spec/namespace/nested/000077500000000000000000000000001360317524000225715ustar00rootroot00000000000000sinatra-2.0.8.1/sinatra-contrib/spec/namespace/nested/foo.erb000066400000000000000000000000031360317524000240370ustar00rootroot00000000000000ho sinatra-2.0.8.1/sinatra-contrib/spec/namespace_spec.rb000066400000000000000000000636021360317524000226550ustar00rootroot00000000000000require 'spec_helper' describe Sinatra::Namespace do verbs = [:get, :head, :post, :put, :delete, :options, :patch] def mock_app(&block) super do register Sinatra::Namespace class_eval(&block) end end def namespace(*args, &block) mock_app { namespace(*args, &block) } end verbs.each do |verb| describe "HTTP #{verb.to_s.upcase}" do it 'prefixes the path with the namespace' do namespace('/foo') { send(verb, '/bar') { 'baz' }} expect(send(verb, '/foo/bar')).to be_ok expect(body).to eq('baz') unless verb == :head expect(send(verb, '/foo/baz')).not_to be_ok end describe 'redirect_to' do it 'redirect within namespace' do namespace('/foo') { send(verb, '/bar') { redirect_to '/foo_bar' }} expect(send(verb, '/foo/bar')).to be_redirect expect(send(verb, '/foo/bar').location).to include("/foo/foo_bar") end end context 'when namespace is a string' do it 'accepts routes with no path' do namespace('/foo') { send(verb) { 'bar' } } expect(send(verb, '/foo')).to be_ok expect(body).to eq('bar') unless verb == :head end it 'accepts the path as a named parameter' do namespace('/foo') { send(verb, '/:bar') { params[:bar] }} expect(send(verb, '/foo/bar')).to be_ok expect(body).to eq('bar') unless verb == :head expect(send(verb, '/foo/baz')).to be_ok expect(body).to eq('baz') unless verb == :head end it 'accepts the path as a regular expression' do namespace('/foo') { send(verb, /\/\d\d/) { 'bar' }} expect(send(verb, '/foo/12')).to be_ok expect(body).to eq 'bar' unless verb == :head expect(send(verb, '/foo/123')).not_to be_ok end end context 'when namespace is a named parameter' do it 'accepts routes with no path' do namespace('/:foo') { send(verb) { 'bar' } } expect(send(verb, '/foo')).to be_ok expect(body).to eq('bar') unless verb == :head end it 'sets the parameter correctly' do namespace('/:foo') { send(verb, '/bar') { params[:foo] }} expect(send(verb, '/foo/bar')).to be_ok expect(body).to eq('foo') unless verb == :head expect(send(verb, '/fox/bar')).to be_ok expect(body).to eq('fox') unless verb == :head expect(send(verb, '/foo/baz')).not_to be_ok end it 'accepts the path as a named parameter' do namespace('/:foo') { send(verb, '/:bar') { params[:bar] }} expect(send(verb, '/foo/bar')).to be_ok expect(body).to eq('bar') unless verb == :head expect(send(verb, '/foo/baz')).to be_ok expect(body).to eq('baz') unless verb == :head end it 'accepts the path as regular expression' do namespace('/:foo') { send(verb, %r{/bar}) { params[:foo] }} expect(send(verb, '/foo/bar')).to be_ok expect(body).to eq('foo') unless verb == :head expect(send(verb, '/fox/bar')).to be_ok expect(body).to eq('fox') unless verb == :head expect(send(verb, '/foo/baz')).not_to be_ok end end context 'when namespace is a regular expression' do it 'accepts routes with no path' do namespace(%r{/foo}) { send(verb) { 'bar' } } expect(send(verb, '/foo')).to be_ok expect(body).to eq('bar') unless verb == :head end it 'accepts the path as a named parameter' do namespace(%r{/foo}) { send(verb, '/:bar') { params[:bar] }} expect(send(verb, '/foo/bar')).to be_ok expect(body).to eq('bar') unless verb == :head expect(send(verb, '/foo/baz')).to be_ok expect(body).to eq('baz') unless verb == :head end it 'accepts the path as a regular expression' do namespace(/\/\d\d/) { send(verb, /\/\d\d/) { 'foo' }} expect(send(verb, '/23/12')).to be_ok expect(body).to eq('foo') unless verb == :head expect(send(verb, '/123/12')).not_to be_ok end describe "before/after filters" do it 'trigger before filter' do ran = false namespace(/\/foo\/([^\/&?]+)\/bar\/([^\/&?]+)\//) { before { ran = true };} send(verb, '/bar/') expect(ran).to eq(false) send(verb, '/foo/1/bar/1/') expect(ran).to eq(true) end it 'trigger after filter' do ran = false namespace(/\/foo\/([^\/&?]+)\/bar\/([^\/&?]+)\//) { after { ran = true };} send(verb, '/bar/') expect(ran).to eq(false) send(verb, '/foo/1/bar/1/') expect(ran).to eq(true) end end describe 'helpers' do it 'are defined using the helpers method' do namespace(/\/foo\/([^\/&?]+)\/bar\/([^\/&?]+)\//) do helpers do def foo 'foo' end end send verb, '' do foo.to_s end end expect(send(verb, '/foo/1/bar/1/')).to be_ok expect(body).to eq('foo') unless verb == :head end end end context 'when namespace is a splat' do it 'accepts the path as a splat' do namespace('/*') { send(verb, '/*') { params[:splat].join ' - ' }} expect(send(verb, '/foo/bar')).to be_ok expect(body).to eq('foo - bar') unless verb == :head end end describe 'before-filters' do specify 'are triggered' do ran = false namespace('/foo') { before { ran = true }} send(verb, '/foo') expect(ran).to be true end specify 'are not triggered for a different namespace' do ran = false namespace('/foo') { before { ran = true }} send(verb, '/fox') expect(ran).to be false end end describe 'after-filters' do specify 'are triggered' do ran = false namespace('/foo') { after { ran = true }} send(verb, '/foo') expect(ran).to be true end specify 'are not triggered for a different namespace' do ran = false namespace('/foo') { after { ran = true }} send(verb, '/fox') expect(ran).to be false end end describe 'conditions' do context 'when the namespace has no prefix' do specify 'are accepted in the namespace' do mock_app do namespace(:host_name => 'example.com') { send(verb) { 'yes' }} send(verb, '/') { 'no' } end send(verb, '/', {}, 'HTTP_HOST' => 'example.com') expect(last_response).to be_ok expect(body).to eq('yes') unless verb == :head send(verb, '/', {}, 'HTTP_HOST' => 'example.org') expect(last_response).to be_ok expect(body).to eq('no') unless verb == :head end specify 'are accepted in the route definition' do namespace :host_name => 'example.com' do send(verb, '/foo', :provides => :txt) { 'ok' } end expect(send(verb, '/foo', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/plain')).to be_ok expect(send(verb, '/foo', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/html')).not_to be_ok expect(send(verb, '/foo', {}, 'HTTP_HOST' => 'example.org', 'HTTP_ACCEPT' => 'text/plain')).not_to be_ok end specify 'are accepted in the before-filter' do ran = false namespace :provides => :txt do before('/foo', :host_name => 'example.com') { ran = true } send(verb, '/*') { 'ok' } end send(verb, '/foo', {}, 'HTTP_HOST' => 'example.org', 'HTTP_ACCEPT' => 'text/plain') expect(ran).to be false send(verb, '/foo', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/html') expect(ran).to be false send(verb, '/bar', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/plain') expect(ran).to be false send(verb, '/foo', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/plain') expect(ran).to be true end specify 'are accepted in the after-filter' do ran = false namespace :provides => :txt do after('/foo', :host_name => 'example.com') { ran = true } send(verb, '/*') { 'ok' } end send(verb, '/foo', {}, 'HTTP_HOST' => 'example.org', 'HTTP_ACCEPT' => 'text/plain') expect(ran).to be false send(verb, '/foo', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/html') expect(ran).to be false send(verb, '/bar', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/plain') expect(ran).to be false send(verb, '/foo', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/plain') expect(ran).to be true end end context 'when the namespace is a string' do specify 'are accepted in the namespace' do namespace '/foo', :host_name => 'example.com' do send(verb) { 'ok' } end expect(send(verb, '/foo', {}, 'HTTP_HOST' => 'example.com')).to be_ok expect(send(verb, '/foo', {}, 'HTTP_HOST' => 'example.org')).not_to be_ok end specify 'are accepted in the before-filter' do namespace '/foo' do before(:host_name => 'example.com') { @yes = 'yes' } send(verb) { @yes || 'no' } end send(verb, '/foo', {}, 'HTTP_HOST' => 'example.com') expect(last_response).to be_ok expect(body).to eq('yes') unless verb == :head send(verb, '/foo', {}, 'HTTP_HOST' => 'example.org') expect(last_response).to be_ok expect(body).to eq('no') unless verb == :head end specify 'are accepted in the after-filter' do ran = false namespace '/foo' do before(:host_name => 'example.com') { ran = true } send(verb) { 'ok' } end send(verb, '/foo', {}, 'HTTP_HOST' => 'example.org') expect(ran).to be false send(verb, '/foo', {}, 'HTTP_HOST' => 'example.com') expect(ran).to be true end specify 'are accepted in the route definition' do namespace '/foo' do send(verb, :host_name => 'example.com') { 'ok' } end expect(send(verb, '/foo', {}, 'HTTP_HOST' => 'example.com')).to be_ok expect(send(verb, '/foo', {}, 'HTTP_HOST' => 'example.org')).not_to be_ok end context 'when the namespace has a condition' do specify 'are accepted in the before-filter' do ran = false namespace '/', :provides => :txt do before(:host_name => 'example.com') { ran = true } send(verb) { 'ok' } end send(verb, '/', {}, 'HTTP_HOST' => 'example.org', 'HTTP_ACCEPT' => 'text/plain') expect(ran).to be false send(verb, '/', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/html') expect(ran).to be false send(verb, '/', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/plain') expect(ran).to be true end specify 'are accepted in the filters' do ran = false namespace '/f', :provides => :txt do before('oo', :host_name => 'example.com') { ran = true } send(verb, '/*') { 'ok' } end send(verb, '/foo', {}, 'HTTP_HOST' => 'example.org', 'HTTP_ACCEPT' => 'text/plain') expect(ran).to be false send(verb, '/foo', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/html') expect(ran).to be false send(verb, '/far', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/plain') expect(ran).to be false send(verb, '/foo', {}, 'HTTP_HOST' => 'example.com', 'HTTP_ACCEPT' => 'text/plain') expect(ran).to be true end end end end describe 'helpers' do it 'are defined using the helpers method' do namespace '/foo' do helpers do def magic 42 end end send verb, '/bar' do magic.to_s end end expect(send(verb, '/foo/bar')).to be_ok expect(body).to eq('42') unless verb == :head end it 'can be defined as normal methods' do namespace '/foo' do def magic 42 end send verb, '/bar' do magic.to_s end end expect(send(verb, '/foo/bar')).to be_ok expect(body).to eq('42') unless verb == :head end it 'can be defined using module mixins' do mixin = Module.new do def magic 42 end end namespace '/foo' do helpers mixin send verb, '/bar' do magic.to_s end end expect(send(verb, '/foo/bar')).to be_ok expect(body).to eq('42') unless verb == :head end specify 'are unavailable outside the namespace where they are defined' do mock_app do namespace '/foo' do def magic 42 end send verb, '/bar' do magic.to_s end end send verb, '/' do magic.to_s end end expect { send verb, '/' }.to raise_error(NameError) end specify 'are unavailable outside the namespace that they are mixed into' do mixin = Module.new do def magic 42 end end mock_app do namespace '/foo' do helpers mixin send verb, '/bar' do magic.to_s end end send verb, '/' do magic.to_s end end expect { send verb, '/' }.to raise_error(NameError) end specify 'are available to nested namespaces' do mock_app do helpers do def magic 42 end end namespace '/foo' do send verb, '/bar' do magic.to_s end end end expect(send(verb, '/foo/bar')).to be_ok expect(body).to eq('42') unless verb == :head end specify 'can call super from nested definitions' do mock_app do helpers do def magic 42 end end namespace '/foo' do def magic super - 19 end send verb, '/bar' do magic.to_s end end end expect(send(verb, '/foo/bar')).to be_ok expect(body).to eq('23') unless verb == :head end end describe 'nesting' do it 'routes to nested namespaces' do namespace '/foo' do namespace '/bar' do send(verb, '/baz') { 'OKAY!!11!'} end end expect(send(verb, '/foo/bar/baz')).to be_ok expect(body).to eq('OKAY!!11!') unless verb == :head end it 'works correctly if deep nesting' do namespace '/a' do namespace '/b' do namespace '/c' do send(verb, '') { 'hey' } end end end expect(send(verb, '/a/b/c')).to be_ok expect(body).to eq('hey') unless verb == :head end it 'exposes helpers to nested namespaces' do namespace '/foo' do helpers do def magic 42 end end namespace '/bar' do send verb, '/baz' do magic.to_s end end end expect(send(verb, '/foo/bar/baz')).to be_ok expect(body).to eq('42') unless verb == :head end specify 'does not provide access to nested helper methods' do namespace '/foo' do namespace '/bar' do def magic 42 end send verb, '/baz' do magic.to_s end end send verb do magic.to_s end end expect { send verb, '/foo' }.to raise_error(NameError) end it 'accepts a nested namespace as a named parameter' do namespace('/:a') { namespace('/:b') { send(verb) { params[:a] }}} expect(send(verb, '/foo/bar')).to be_ok expect(body).to eq('foo') unless verb == :head end end describe 'error handling' do it 'can be customized using the not_found block' do namespace('/de') do not_found { 'nicht gefunden' } end expect(send(verb, '/foo').status).to eq 404 expect(last_response.body).not_to eq 'nicht gefunden' unless verb == :head expect(get('/en/foo').status).to eq 404 expect(last_response.body).not_to eq 'nicht gefunden' unless verb == :head expect(get('/de/foo').status).to eq 404 expect(last_response.body).to eq 'nicht gefunden' unless verb == :head end it 'can be customized for specific error codes' do namespace('/de') do error(404) { 'nicht gefunden' } end expect(send(verb, '/foo').status).to eq 404 expect(last_response.body).not_to eq 'nicht gefunden' unless verb == :head expect(get('/en/foo').status).to eq 404 expect(last_response.body).not_to eq 'nicht gefunden' unless verb == :head expect(get('/de/foo').status).to eq 404 expect(last_response.body).to eq 'nicht gefunden' unless verb == :head end it 'falls back to the handler defined in the base app' do mock_app do error(404) { 'not found...' } namespace('/en') do end namespace('/de') do error(404) { 'nicht gefunden' } end end expect(send(verb, '/foo').status).to eq 404 expect(last_response.body).to eq 'not found...' unless verb == :head expect(get('/en/foo').status).to eq 404 expect(last_response.body).to eq 'not found...' unless verb == :head expect(get('/de/foo').status).to eq 404 expect(last_response.body).to eq 'nicht gefunden' unless verb == :head end it 'can be customized for specific Exception classes' do mock_app do class AError < StandardError; end class BError < AError; end error(AError) do body('auth failed') 401 end namespace('/en') do get '/foo' do raise BError end end namespace('/de') do error(AError) do body('methode nicht erlaubt') 406 end get '/foo' do raise BError end end end expect(get('/en/foo').status).to eq 401 expect(last_response.body).to eq 'auth failed' unless verb == :head expect(get('/de/foo').status).to eq 406 expect(last_response.body).to eq 'methode nicht erlaubt' unless verb == :head end it "allows custom error handlers when namespace is declared as /en/:id. Issue #119" do mock_app { class CError < StandardError; end error { raise "should not come here" } namespace('/en/:id') do error(CError) { 201 } get '/?' do raise CError end end } expect(get('/en/1').status).to eq(201) end end unless verb == :head describe 'templates' do specify 'default to the base app\'s template' do mock_app do template(:foo) { 'hi' } send(verb, '/') { erb :foo } namespace '/foo' do send(verb) { erb :foo } end end expect(send(verb, '/').body).to eq 'hi' expect(send(verb, '/foo').body).to eq 'hi' end specify 'can be nested' do mock_app do template(:foo) { 'hi' } send(verb, '/') { erb :foo } namespace '/foo' do template(:foo) { 'ho' } send(verb) { erb :foo } end end expect(send(verb, '/').body).to eq 'hi' expect(send(verb, '/foo').body).to eq 'ho' end specify 'can use a custom views directory' do mock_app do set :views, File.expand_path('../namespace', __FILE__) send(verb, '/') { erb :foo } namespace('/foo') do set :views, File.expand_path('../namespace/nested', __FILE__) send(verb) { erb :foo } end end expect(send(verb, '/').body).to eq "hi\n" expect(send(verb, '/foo').body).to eq "ho\n" end specify 'default to the base app\'s layout' do mock_app do layout { 'he said: <%= yield %>' } template(:foo) { 'hi' } send(verb, '/') { erb :foo } namespace '/foo' do template(:foo) { 'ho' } send(verb) { erb :foo } end end expect(send(verb, '/').body).to eq 'he said: hi' expect(send(verb, '/foo').body).to eq 'he said: ho' end specify 'can define nested layouts' do mock_app do layout { 'Hello <%= yield %>!' } template(:foo) { 'World' } send(verb, '/') { erb :foo } namespace '/foo' do layout { 'Hi <%= yield %>!' } send(verb) { erb :foo } end end expect(send(verb, '/').body).to eq 'Hello World!' expect(send(verb, '/foo').body).to eq 'Hi World!' end end end describe 'extensions' do specify 'provide read access to settings' do value = nil mock_app do set :foo, 42 namespace '/foo' do value = foo end end expect(value).to eq 42 end specify 'can be registered within a namespace' do a = b = nil extension = Module.new { define_method(:views) { 'CUSTOM!!!' } } mock_app do namespace '/' do register extension a = views end b = views end expect(a).to eq 'CUSTOM!!!' expect(b).not_to eq 'CUSTOM!!!' end specify 'trigger the route_added hook' do route = nil extension = Module.new extension.singleton_class.class_eval do define_method(:route_added) { |*r| route = r } end mock_app do namespace '/f' do register extension get('oo') { } end get('/bar') { } end expect(route[1]).to eq(Mustermann.new '/foo') end specify 'prevent app-global settings from being changed' do expect { namespace('/') { set :foo, :bar }}.to raise_error(ArgumentError) end end end end describe 'settings' do it 'provides access to top-level settings' do mock_app do set :foo, 'ok' namespace '/foo' do get '/bar' do settings.foo end end end expect(get('/foo/bar').status).to eq(200) expect(last_response.body).to eq('ok') end it 'uses some repro' do mock_app do set :foo, 42 namespace '/foo' do get '/bar' do #settings.respond_to?(:foo).to_s settings.foo.to_s end end end expect(get('/foo/bar').status).to eq(200) expect(last_response.body).to eq('42') end it 'allows checking setting existence with respond_to?' do mock_app do set :foo, 42 namespace '/foo' do get '/bar' do settings.respond_to?(:foo).to_s end end end expect(get('/foo/bar').status).to eq(200) expect(last_response.body).to eq('true') end it 'avoids executing filters even if prefix matches with other namespace' do mock_app do helpers do def dump_args(*args) args.inspect end end namespace '/foo' do helpers do def dump_args(*args) super(:foo, *args) end end get('') { dump_args } end namespace '/foo-bar' do helpers do def dump_args(*args) super(:foo_bar, *args) end end get('') { dump_args } end end get '/foo-bar' expect(last_response.body).to eq('[:foo_bar]') end end end sinatra-2.0.8.1/sinatra-contrib/spec/okjson.rb000066400000000000000000000326341360317524000212130ustar00rootroot00000000000000# Copyright 2011 Keith Rarick # # 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. # See https://github.com/kr/okjson for updates. require 'stringio' # Some parts adapted from # http://golang.org/src/pkg/json/decode.go and # http://golang.org/src/pkg/utf8/utf8.go module OkJson extend self # Decodes a json document in string s and # returns the corresponding ruby value. # String s must be valid UTF-8. If you have # a string in some other encoding, convert # it first. # # String values in the resulting structure # will be UTF-8. def decode(s) ts = lex(s) v, ts = textparse(ts) if ts.length > 0 raise Error, 'trailing garbage' end v end # Parses a "json text" in the sense of RFC 4627. # Returns the parsed value and any trailing tokens. # Note: this is almost the same as valparse, # except that it does not accept atomic values. def textparse(ts) if ts.length < 0 raise Error, 'empty' end typ, _, val = ts[0] case typ when '{' then objparse(ts) when '[' then arrparse(ts) else raise Error, "unexpected #{val.inspect}" end end # Parses a "value" in the sense of RFC 4627. # Returns the parsed value and any trailing tokens. def valparse(ts) if ts.length < 0 raise Error, 'empty' end typ, _, val = ts[0] case typ when '{' then objparse(ts) when '[' then arrparse(ts) when :val,:str then [val, ts[1..-1]] else raise Error, "unexpected #{val.inspect}" end end # Parses an "object" in the sense of RFC 4627. # Returns the parsed value and any trailing tokens. def objparse(ts) ts = eat('{', ts) obj = {} if ts[0][0] == '}' return obj, ts[1..-1] end k, v, ts = pairparse(ts) obj[k] = v if ts[0][0] == '}' return obj, ts[1..-1] end loop do ts = eat(',', ts) k, v, ts = pairparse(ts) obj[k] = v if ts[0][0] == '}' return obj, ts[1..-1] end end end # Parses a "member" in the sense of RFC 4627. # Returns the parsed values and any trailing tokens. def pairparse(ts) (typ, _, k), ts = ts[0], ts[1..-1] if typ != :str raise Error, "unexpected #{k.inspect}" end ts = eat(':', ts) v, ts = valparse(ts) [k, v, ts] end # Parses an "array" in the sense of RFC 4627. # Returns the parsed value and any trailing tokens. def arrparse(ts) ts = eat('[', ts) arr = [] if ts[0][0] == ']' return arr, ts[1..-1] end v, ts = valparse(ts) arr << v if ts[0][0] == ']' return arr, ts[1..-1] end loop do ts = eat(',', ts) v, ts = valparse(ts) arr << v if ts[0][0] == ']' return arr, ts[1..-1] end end end def eat(typ, ts) if ts[0][0] != typ raise Error, "expected #{typ} (got #{ts[0].inspect})" end ts[1..-1] end # Sans s and returns a list of json tokens, # excluding white space (as defined in RFC 4627). def lex(s) ts = [] while s.length > 0 typ, lexeme, val = tok(s) if typ == nil raise Error, "invalid character at #{s[0,10].inspect}" end if typ != :space ts << [typ, lexeme, val] end s = s[lexeme.length..-1] end ts end # Scans the first token in s and # returns a 3-element list, or nil # if no such token exists. # # The first list element is one of # '{', '}', ':', ',', '[', ']', # :val, :str, and :space. # # The second element is the lexeme. # # The third element is the value of the # token for :val and :str, otherwise # it is the lexeme. def tok(s) case s[0] when ?{ then ['{', s[0,1], s[0,1]] when ?} then ['}', s[0,1], s[0,1]] when ?: then [':', s[0,1], s[0,1]] when ?, then [',', s[0,1], s[0,1]] when ?[ then ['[', s[0,1], s[0,1]] when ?] then [']', s[0,1], s[0,1]] when ?n then nulltok(s) when ?t then truetok(s) when ?f then falsetok(s) when ?" then strtok(s) when Spc then [:space, s[0,1], s[0,1]] when ?\t then [:space, s[0,1], s[0,1]] when ?\n then [:space, s[0,1], s[0,1]] when ?\r then [:space, s[0,1], s[0,1]] else numtok(s) end end def nulltok(s); s[0,4] == 'null' && [:val, 'null', nil] end def truetok(s); s[0,4] == 'true' && [:val, 'true', true] end def falsetok(s); s[0,5] == 'false' && [:val, 'false', false] end def numtok(s) m = /-?([1-9][0-9]+|[0-9])([.][0-9]+)?([eE][+-]?[0-9]+)?/.match(s) if m && m.begin(0) == 0 if m[3] && !m[2] [:val, m[0], Integer(m[1])*(10**Integer(m[3][1..-1]))] elsif m[2] [:val, m[0], Float(m[0])] else [:val, m[0], Integer(m[0])] end end end def strtok(s) m = /"([^"\\]|\\["\/\\bfnrt]|\\u[0-9a-fA-F]{4})*"/.match(s) if ! m raise Error, "invalid string literal at #{abbrev(s)}" end [:str, m[0], unquote(m[0])] end def abbrev(s) t = s[0,10] p = t['`'] t = t[0,p] if p t = t + '...' if t.length < s.length '`' + t + '`' end # Converts a quoted json string literal q into a UTF-8-encoded string. # The rules are different than for Ruby, so we cannot use eval. # Unquote will raise an error if q contains control characters. def unquote(q) q = q[1...-1] a = q.dup # allocate a big enough string r, w = 0, 0 while r < q.length c = q[r] case true when c == ?\\ r += 1 if r >= q.length raise Error, "string literal ends with a \"\\\": \"#{q}\"" end case q[r] when ?",?\\,?/,?' a[w] = q[r] r += 1 w += 1 when ?b,?f,?n,?r,?t a[w] = Unesc[q[r]] r += 1 w += 1 when ?u r += 1 uchar = begin hexdec4(q[r,4]) rescue RuntimeError => e raise Error, "invalid escape sequence \\u#{q[r,4]}: #{e}" end r += 4 if surrogate? uchar if q.length >= r+6 uchar1 = hexdec4(q[r+2,4]) uchar = subst(uchar, uchar1) if uchar != Ucharerr # A valid pair; consume. r += 6 end end end w += ucharenc(a, w, uchar) else raise Error, "invalid escape char #{q[r]} in \"#{q}\"" end when c == ?", c < Spc raise Error, "invalid character in string literal \"#{q}\"" else # Copy anything else byte-for-byte. # Valid UTF-8 will remain valid UTF-8. # Invalid UTF-8 will remain invalid UTF-8. a[w] = c r += 1 w += 1 end end a[0,w] end # Encodes unicode character u as UTF-8 # bytes in string a at position i. # Returns the number of bytes written. def ucharenc(a, i, u) case true when u <= Uchar1max a[i] = (u & 0xff).chr 1 when u <= Uchar2max a[i+0] = (Utag2 | ((u>>6)&0xff)).chr a[i+1] = (Utagx | (u&Umaskx)).chr 2 when u <= Uchar3max a[i+0] = (Utag3 | ((u>>12)&0xff)).chr a[i+1] = (Utagx | ((u>>6)&Umaskx)).chr a[i+2] = (Utagx | (u&Umaskx)).chr 3 else a[i+0] = (Utag4 | ((u>>18)&0xff)).chr a[i+1] = (Utagx | ((u>>12)&Umaskx)).chr a[i+2] = (Utagx | ((u>>6)&Umaskx)).chr a[i+3] = (Utagx | (u&Umaskx)).chr 4 end end def hexdec4(s) if s.length != 4 raise Error, 'short' end (nibble(s[0])<<12) | (nibble(s[1])<<8) | (nibble(s[2])<<4) | nibble(s[3]) end def subst(u1, u2) if Usurr1 <= u1 && u1 < Usurr2 && Usurr2 <= u2 && u2 < Usurr3 return ((u1-Usurr1)<<10) | (u2-Usurr2) + Usurrself end return Ucharerr end def unsubst(u) if u < Usurrself || u > Umax || surrogate?(u) return Ucharerr, Ucharerr end u -= Usurrself [Usurr1 + ((u>>10)&0x3ff), Usurr2 + (u&0x3ff)] end def surrogate?(u) Usurr1 <= u && u < Usurr3 end def nibble(c) case true when ?0 <= c && c <= ?9 then c.ord - ?0.ord when ?a <= c && c <= ?z then c.ord - ?a.ord + 10 when ?A <= c && c <= ?Z then c.ord - ?A.ord + 10 else raise Error, "invalid hex code #{c}" end end # Encodes x into a json text. It may contain only # Array, Hash, String, Numeric, true, false, nil. # (Note, this list excludes Symbol.) # X itself must be an Array or a Hash. # No other value can be encoded, and an error will # be raised if x contains any other value, such as # Nan, Infinity, Symbol, and Proc, or if a Hash key # is not a String. # Strings contained in x must be valid UTF-8. def encode(x) case x when Hash then objenc(x) when Array then arrenc(x) else raise Error, 'root value must be an Array or a Hash' end end def valenc(x) case x when Hash then objenc(x) when Array then arrenc(x) when String then strenc(x) when Numeric then numenc(x) when true then "true" when false then "false" when nil then "null" else raise Error, "cannot encode #{x.class}: #{x.inspect}" end end def objenc(x) '{' + x.map{|k,v| keyenc(k) + ':' + valenc(v)}.join(',') + '}' end def arrenc(a) '[' + a.map{|x| valenc(x)}.join(',') + ']' end def keyenc(k) case k when String then strenc(k) else raise Error, "Hash key is not a string: #{k.inspect}" end end def strenc(s) t = StringIO.new t.putc(?") r = 0 while r < s.length case s[r] when ?" then t.print('\\"') when ?\\ then t.print('\\\\') when ?\b then t.print('\\b') when ?\f then t.print('\\f') when ?\n then t.print('\\n') when ?\r then t.print('\\r') when ?\t then t.print('\\t') else c = s[r] case true when Spc <= c && c <= ?~ t.putc(c) when true u, size = uchardec(s, r) r += size - 1 # we add one more at the bottom of the loop if u < 0x10000 t.print('\\u') hexenc4(t, u) else u1, u2 = unsubst(u) t.print('\\u') hexenc4(t, u1) t.print('\\u') hexenc4(t, u2) end else # invalid byte; skip it end end r += 1 end t.putc(?") t.string end def hexenc4(t, u) t.putc(Hex[(u>>12)&0xf]) t.putc(Hex[(u>>8)&0xf]) t.putc(Hex[(u>>4)&0xf]) t.putc(Hex[u&0xf]) end def numenc(x) if x.nan? || x.infinite? return 'null' end rescue nil "#{x}" end # Decodes unicode character u from UTF-8 # bytes in string s at position i. # Returns u and the number of bytes read. def uchardec(s, i) n = s.length - i return [Ucharerr, 1] if n < 1 c0 = s[i].ord # 1-byte, 7-bit sequence? if c0 < Utagx return [c0, 1] end # unexpected continuation byte? return [Ucharerr, 1] if c0 < Utag2 # need continuation byte return [Ucharerr, 1] if n < 2 c1 = s[i+1].ord return [Ucharerr, 1] if c1 < Utagx || Utag2 <= c1 # 2-byte, 11-bit sequence? if c0 < Utag3 u = (c0&Umask2)<<6 | (c1&Umaskx) return [Ucharerr, 1] if u <= Uchar1max return [u, 2] end # need second continuation byte return [Ucharerr, 1] if n < 3 c2 = s[i+2].ord return [Ucharerr, 1] if c2 < Utagx || Utag2 <= c2 # 3-byte, 16-bit sequence? if c0 < Utag4 u = (c0&Umask3)<<12 | (c1&Umaskx)<<6 | (c2&Umaskx) return [Ucharerr, 1] if u <= Uchar2max return [u, 3] end # need third continuation byte return [Ucharerr, 1] if n < 4 c3 = s[i+3].ord return [Ucharerr, 1] if c3 < Utagx || Utag2 <= c3 # 4-byte, 21-bit sequence? if c0 < Utag5 u = (c0&Umask4)<<18 | (c1&Umaskx)<<12 | (c2&Umaskx)<<6 | (c3&Umaskx) return [Ucharerr, 1] if u <= Uchar3max return [u, 4] end return [Ucharerr, 1] end class Error < ::StandardError end Utagx = 0x80 # 1000 0000 Utag2 = 0xc0 # 1100 0000 Utag3 = 0xe0 # 1110 0000 Utag4 = 0xf0 # 1111 0000 Utag5 = 0xF8 # 1111 1000 Umaskx = 0x3f # 0011 1111 Umask2 = 0x1f # 0001 1111 Umask3 = 0x0f # 0000 1111 Umask4 = 0x07 # 0000 0111 Uchar1max = (1<<7) - 1 Uchar2max = (1<<11) - 1 Uchar3max = (1<<16) - 1 Ucharerr = 0xFFFD # unicode "replacement char" Usurrself = 0x10000 Usurr1 = 0xd800 Usurr2 = 0xdc00 Usurr3 = 0xe000 Umax = 0x10ffff Spc = ' '[0] Unesc = {?b=>?\b, ?f=>?\f, ?n=>?\n, ?r=>?\r, ?t=>?\t} Hex = '0123456789abcdef' end sinatra-2.0.8.1/sinatra-contrib/spec/reloader/000077500000000000000000000000001360317524000211505ustar00rootroot00000000000000sinatra-2.0.8.1/sinatra-contrib/spec/reloader/app.rb.erb000066400000000000000000000012701360317524000230240ustar00rootroot00000000000000class <%= name %> < <%= parent %> <% if enable_reloader %> register Sinatra::Reloader enable :reloader <% end %> <% unless inline_templates.nil? %> enable :inline_templates <% end %> <% extensions.each do |extension| %> register <%= extension %> <% end %> <% middlewares.each do |middleware| %> use <%= middleware %> <% end %> <% filters.each do |filter| %> <%= filter %> <% end %> <% errors.each do |number, code| %> error <%= number %> do <%= code %> end <% end %> <% routes.each do |route| %> <%= route %> <% end %> end <% unless inline_templates.nil? %> __END__ <% inline_templates.each_pair do |name, content| %> @@<%= name %> <%= content %> <% end %> <% end %> sinatra-2.0.8.1/sinatra-contrib/spec/reloader_spec.rb000066400000000000000000000337731360317524000225240ustar00rootroot00000000000000require 'spec_helper' require 'fileutils' describe Sinatra::Reloader do # Returns the temporary directory. def tmp_dir File.expand_path('../../tmp', __FILE__) end # Returns the path of the Sinatra application file created by # +setup_example_app+. def app_file_path File.join(tmp_dir, "example_app_#{$example_app_counter}.rb") end # Returns the name of the Sinatra application created by # +setup_example_app+: 'ExampleApp1' for the first application, # 'ExampleApp2' fo the second one, and so on... def app_name "ExampleApp#{$example_app_counter}" end # Returns the (constant of the) Sinatra application created by # +setup_example_app+. def app_const Module.const_get(app_name) end # Writes a file with a Sinatra application using the template # located at specs/reloader/app.rb.erb. It expects an # +options+ hash, with an array of strings containing the # application's routes (+:routes+ key), a hash with the inline # template's names as keys and the bodys as values # (+:inline_templates+ key) and an optional application name # (+:name+) otherwise +app_name+ is used. # # It ensures to change the written file's mtime when it already # exists. def write_app_file(options={}) options[:routes] ||= ['get("/foo") { erb :foo }'] options[:inline_templates] ||= nil options[:extensions] ||= [] options[:middlewares] ||= [] options[:filters] ||= [] options[:errors] ||= {} options[:name] ||= app_name options[:enable_reloader] = true unless options[:enable_reloader] === false options[:parent] ||= 'Sinatra::Base' update_file(app_file_path) do |f| template_path = File.expand_path('../reloader/app.rb.erb', __FILE__) template = Tilt.new(template_path, nil, :trim => '<>') f.write template.render(Object.new, options) end end alias update_app_file write_app_file # It calls File.open(path, 'w', &block) all the times # needed to change the file's mtime. def update_file(path, &block) original_mtime = File.exist?(path) ? File.mtime(path) : Time.at(0) new_time = original_mtime + 1 File.open(path, 'w', &block) File.utime(new_time, new_time, path) end # Writes a Sinatra application to a file, requires the file, sets # the new application as the one being tested and enables the # reloader. def setup_example_app(options={}) $example_app_counter ||= 0 $example_app_counter += 1 FileUtils.mkdir_p(tmp_dir) write_app_file(options) $LOADED_FEATURES.delete app_file_path require app_file_path self.app = app_const app_const.enable :reloader end after(:all) { FileUtils.rm_rf(tmp_dir) } describe "default route reloading mechanism" do before(:each) do setup_example_app(:routes => ['get("/foo") { "foo" }']) end it "doesn't mess up the application" do expect(get('/foo').body).to eq('foo') end it "knows when a route has been modified" do update_app_file(:routes => ['get("/foo") { "bar" }']) expect(get('/foo').body).to eq('bar') end it "knows when a route has been added" do update_app_file( :routes => ['get("/foo") { "foo" }', 'get("/bar") { "bar" }'] ) expect(get('/foo').body).to eq('foo') expect(get('/bar').body).to eq('bar') end it "knows when a route has been removed" do update_app_file(:routes => ['get("/bar") { "bar" }']) expect(get('/foo').status).to eq(404) end it "doesn't try to reload a removed file" do update_app_file(:routes => ['get("/foo") { "i shall not be reloaded" }']) FileUtils.rm app_file_path expect(get('/foo').body.strip).to eq('foo') end end describe "default inline templates reloading mechanism" do before(:each) do setup_example_app( :routes => ['get("/foo") { erb :foo }'], :inline_templates => { :foo => 'foo' } ) end it "doesn't mess up the application" do expect(get('/foo').body.strip).to eq('foo') end it "reloads inline templates in the app file" do update_app_file( :routes => ['get("/foo") { erb :foo }'], :inline_templates => { :foo => 'bar' } ) expect(get('/foo').body.strip).to eq('bar') end it "reloads inline templates in other file" do setup_example_app(:routes => ['get("/foo") { erb :foo }']) template_file_path = File.join(tmp_dir, 'templates.rb') File.open(template_file_path, 'w') do |f| f.write "__END__\n\n@@foo\nfoo" end require template_file_path app_const.inline_templates= template_file_path expect(get('/foo').body.strip).to eq('foo') update_file(template_file_path) do |f| f.write "__END__\n\n@@foo\nbar" end expect(get('/foo').body.strip).to eq('bar') end end describe "default middleware reloading mechanism" do it "knows when a middleware has been added" do setup_example_app(:routes => ['get("/foo") { "foo" }']) update_app_file( :routes => ['get("/foo") { "foo" }'], :middlewares => [Rack::Head] ) get('/foo') # ...to perform the reload expect(app_const.middleware).not_to be_empty end it "knows when a middleware has been removed" do setup_example_app( :routes => ['get("/foo") { "foo" }'], :middlewares => [Rack::Head] ) update_app_file(:routes => ['get("/foo") { "foo" }']) get('/foo') # ...to perform the reload expect(app_const.middleware).to be_empty end end describe "default filter reloading mechanism" do it "knows when a before filter has been added" do setup_example_app(:routes => ['get("/foo") { "foo" }']) expect { update_app_file( :routes => ['get("/foo") { "foo" }'], :filters => ['before { @hi = "hi" }'] ) get('/foo') # ...to perform the reload }.to change { app_const.filters[:before].size }.by(1) end it "knows when an after filter has been added" do setup_example_app(:routes => ['get("/foo") { "foo" }']) expect { update_app_file( :routes => ['get("/foo") { "foo" }'], :filters => ['after { @bye = "bye" }'] ) get('/foo') # ...to perform the reload }.to change { app_const.filters[:after].size }.by(1) end it "knows when a before filter has been removed" do setup_example_app( :routes => ['get("/foo") { "foo" }'], :filters => ['before { @hi = "hi" }'] ) expect { update_app_file(:routes => ['get("/foo") { "foo" }']) get('/foo') # ...to perform the reload }.to change { app_const.filters[:before].size }.by(-1) end it "knows when an after filter has been removed" do setup_example_app( :routes => ['get("/foo") { "foo" }'], :filters => ['after { @bye = "bye" }'] ) expect { update_app_file(:routes => ['get("/foo") { "foo" }']) get('/foo') # ...to perform the reload }.to change { app_const.filters[:after].size }.by(-1) end end describe "error reloading" do before do setup_example_app( :routes => ['get("/secret") { 403 }'], :errors => { 403 => "'Access forbiden'" } ) end it "doesn't mess up the application" do expect(get('/secret')).to be_client_error expect(get('/secret').body.strip).to eq('Access forbiden') end it "knows when a error has been added" do update_app_file(:errors => { 404 => "'Nowhere'" }) expect(get('/nowhere')).to be_not_found expect(get('/nowhere').body).to eq('Nowhere') end it "knows when a error has been removed" do update_app_file(:routes => ['get("/secret") { 403 }']) expect(get('/secret')).to be_client_error expect(get('/secret').body).not_to eq('Access forbiden') end it "knows when a error has been modified" do update_app_file( :routes => ['get("/secret") { 403 }'], :errors => { 403 => "'What are you doing here?'" } ) expect(get('/secret')).to be_client_error expect(get('/secret').body).to eq('What are you doing here?') end end describe "extension reloading" do it "doesn't duplicate routes with every reload" do module ::RouteExtension def self.registered(klass) klass.get('/bar') { 'bar' } end end setup_example_app( :routes => ['get("/foo") { "foo" }'], :extensions => ['RouteExtension'] ) expect { update_app_file( :routes => ['get("/foo") { "foo" }'], :extensions => ['RouteExtension'] ) get('/foo') # ...to perform the reload }.to_not change { app_const.routes['GET'].size } end it "doesn't duplicate middleware with every reload" do module ::MiddlewareExtension def self.registered(klass) klass.use Rack::Head end end setup_example_app( :routes => ['get("/foo") { "foo" }'], :extensions => ['MiddlewareExtension'] ) expect { update_app_file( :routes => ['get("/foo") { "foo" }'], :extensions => ['MiddlewareExtension'] ) get('/foo') # ...to perform the reload }.to_not change { app_const.middleware.size } end it "doesn't duplicate before filters with every reload" do module ::BeforeFilterExtension def self.registered(klass) klass.before { @hi = 'hi' } end end setup_example_app( :routes => ['get("/foo") { "foo" }'], :extensions => ['BeforeFilterExtension'] ) expect { update_app_file( :routes => ['get("/foo") { "foo" }'], :extensions => ['BeforeFilterExtension'] ) get('/foo') # ...to perform the reload }.to_not change { app_const.filters[:before].size } end it "doesn't duplicate after filters with every reload" do module ::AfterFilterExtension def self.registered(klass) klass.after { @bye = 'bye' } end end setup_example_app( :routes => ['get("/foo") { "foo" }'], :extensions => ['AfterFilterExtension'] ) expect { update_app_file( :routes => ['get("/foo") { "foo" }'], :extensions => ['AfterFilterExtension'] ) get('/foo') # ...to perform the reload }.to_not change { app_const.filters[:after].size } end end describe ".dont_reload" do before(:each) do setup_example_app( :routes => ['get("/foo") { erb :foo }'], :inline_templates => { :foo => 'foo' } ) end it "allows to specify a file to stop from being reloaded" do app_const.dont_reload app_file_path update_app_file(:routes => ['get("/foo") { "bar" }']) expect(get('/foo').body.strip).to eq('foo') end it "allows to specify a glob to stop matching files from being reloaded" do app_const.dont_reload '**/*.rb' update_app_file(:routes => ['get("/foo") { "bar" }']) expect(get('/foo').body.strip).to eq('foo') end it "doesn't interfere with other application's reloading policy" do app_const.dont_reload '**/*.rb' setup_example_app(:routes => ['get("/foo") { "foo" }']) update_app_file(:routes => ['get("/foo") { "bar" }']) expect(get('/foo').body.strip).to eq('bar') end end describe ".also_reload" do before(:each) do setup_example_app(:routes => ['get("/foo") { Foo.foo }']) @foo_path = File.join(tmp_dir, 'foo.rb') update_file(@foo_path) do |f| f.write 'class Foo; def self.foo() "foo" end end' end $LOADED_FEATURES.delete @foo_path require @foo_path app_const.also_reload @foo_path end it "allows to specify a file to be reloaded" do expect(get('/foo').body.strip).to eq('foo') update_file(@foo_path) do |f| f.write 'class Foo; def self.foo() "bar" end end' end expect(get('/foo').body.strip).to eq('bar') end it "allows to specify glob to reaload matching files" do expect(get('/foo').body.strip).to eq('foo') update_file(@foo_path) do |f| f.write 'class Foo; def self.foo() "bar" end end' end expect(get('/foo').body.strip).to eq('bar') end it "doesn't try to reload a removed file" do update_file(@foo_path) do |f| f.write 'class Foo; def self.foo() "bar" end end' end FileUtils.rm @foo_path expect(get('/foo').body.strip).to eq('foo') end it "doesn't interfere with other application's reloading policy" do app_const.also_reload '**/*.rb' setup_example_app(:routes => ['get("/foo") { Foo.foo }']) expect(get('/foo').body.strip).to eq('foo') update_file(@foo_path) do |f| f.write 'class Foo; def self.foo() "bar" end end' end expect(get('/foo').body.strip).to eq('foo') end end describe ".after_reload" do before(:each) do setup_example_app(:routes => ['get("/foo") { Foo.foo }']) @foo_path = File.join(tmp_dir, 'foo.rb') update_file(@foo_path) do |f| f.write 'class Foo; def self.foo() "foo" end end' end $LOADED_FEATURES.delete @foo_path require @foo_path app_const.also_reload @foo_path end it "allows block execution after reloading files" do app_const.after_reload do $reloaded = true end expect($reloaded).to eq(nil) expect(get('/foo').body.strip).to eq('foo') update_file(@foo_path) do |f| f.write 'class Foo; def self.foo() "bar" end end' end expect($reloaded).to eq(true) end end it "automatically registers the reloader in the subclasses" do class ::Parent < Sinatra::Base register Sinatra::Reloader enable :reloader end setup_example_app( :routes => ['get("/foo") { "foo" }'], :enable_reloader => false, :parent => 'Parent' ) update_app_file( :routes => ['get("/foo") { "bar" }'], :enable_reloader => false, :parent => 'Parent' ) expect(get('/foo').body).to eq('bar') end end sinatra-2.0.8.1/sinatra-contrib/spec/required_params_spec.rb000066400000000000000000000043051360317524000240770ustar00rootroot00000000000000require_relative 'spec_helper' describe Sinatra::RequiredParams do context "#required_params" do context "simple keys" do before do mock_app do helpers Sinatra::RequiredParams get('/') { required_params(:p1, :p2) } end end it 'return 400 if required params do not exist' do get('/') expect(last_response.status).to eq(400) end it 'return 400 if required params do not exist partially' do get('/', :p1 => 1) expect(last_response.status).to eq(400) end it 'return 200 if required params exist' do get('/', :p1 => 1, :p2 => 2) expect(last_response.status).to eq(200) end it 'return 200 if required params exist with array' do get('/', :p1 => 1, :p2 => [31, 32, 33]) expect(last_response.status).to eq(200) end end context "hash keys" do before do mock_app do helpers Sinatra::RequiredParams get('/') { required_params(:p1, :p2 => :p21) } end end it 'return 400 if required params do not exist' do get('/') expect(last_response.status).to eq(400) end it 'return 200 if required params exist' do get('/', :p1 => 1, :p2 => {:p21 => 21}) expect(last_response.status).to eq(200) end it 'return 400 if p2 is not a hash' do get('/', :p1 => 1, :p2 => 2) expect(last_response.status).to eq(400) end end context "complex keys" do before do mock_app do helpers Sinatra::RequiredParams get('/') { required_params(:p1 => [:p11, {:p12 => :p121, :p122 => [:p123, {:p124 => :p1241}]}]) } end end it 'return 400 if required params do not exist' do get('/') expect(last_response.status).to eq(400) end it 'return 200 if required params exist' do get('/', :p1 => {:p11 => 11, :p12 => {:p121 => 121}, :p122 => {:p123 => 123, :p124 => {:p1241 => 1241}}}) expect(last_response.status).to eq(200) end end end context "#_required_params" do it "is invisible" do expect { _required_params }.to raise_error(NameError) end end end sinatra-2.0.8.1/sinatra-contrib/spec/respond_with/000077500000000000000000000000001360317524000220605ustar00rootroot00000000000000sinatra-2.0.8.1/sinatra-contrib/spec/respond_with/bar.erb000066400000000000000000000000121360317524000233070ustar00rootroot00000000000000guten Tag!sinatra-2.0.8.1/sinatra-contrib/spec/respond_with/bar.json.erb000066400000000000000000000000051360317524000242610ustar00rootroot00000000000000json!sinatra-2.0.8.1/sinatra-contrib/spec/respond_with/baz.yajl000066400000000000000000000000171360317524000235130ustar00rootroot00000000000000json = "yajl!" sinatra-2.0.8.1/sinatra-contrib/spec/respond_with/foo.html.erb000066400000000000000000000000221360317524000242720ustar00rootroot00000000000000Hello <%= name %>!sinatra-2.0.8.1/sinatra-contrib/spec/respond_with/not_html.sass000066400000000000000000000000221360317524000245710ustar00rootroot00000000000000body color: red sinatra-2.0.8.1/sinatra-contrib/spec/respond_with_spec.rb000066400000000000000000000207261360317524000234260ustar00rootroot00000000000000require 'multi_json' require 'spec_helper' require 'okjson' describe Sinatra::RespondWith do def respond_app(&block) mock_app do set :app_file, __FILE__ set :views, root + '/respond_with' register Sinatra::RespondWith class_eval(&block) end end def respond_to(*args, &block) respond_app { get('/') { respond_to(*args, &block) } } end def respond_with(*args, &block) respond_app { get('/') { respond_with(*args, &block) } } end def req(*types) path = types.shift if types.first.is_a?(String) && types.first.start_with?('/') accept = types.map { |t| Sinatra::Base.mime_type(t).to_s }.join ',' get (path || '/'), {}, 'HTTP_ACCEPT' => accept end describe "Helpers#respond_to" do it 'allows defining handlers by file extensions' do respond_to do |format| format.html { "html!" } format.json { "json!" } end expect(req(:html).body).to eq("html!") expect(req(:json).body).to eq("json!") end it 'respects quality' do respond_to do |format| format.html { "html!" } format.json { "json!" } end expect(req("text/html;q=0.7, application/json;q=0.3").body).to eq("html!") expect(req("text/html;q=0.3, application/json;q=0.7").body).to eq("json!") end it 'allows using mime types' do respond_to do |format| format.on('text/html') { "html!" } format.json { "json!" } end expect(req(:html).body).to eq("html!") end it 'allows using wildcards in format matchers' do respond_to do |format| format.on('text/*') { "text!" } format.json { "json!" } end expect(req(:html).body).to eq("text!") end it 'allows using catch all wildcards in format matchers' do respond_to do |format| format.on('*/*') { "anything!" } format.json { "json!" } end expect(req(:html).body).to eq("anything!") end it 'prefers concret over generic' do respond_to do |format| format.on('text/*') { "text!" } format.on('*/*') { "anything!" } format.json { "json!" } end expect(req(:json).body).to eq("json!") expect(req(:html).body).to eq("text!") end it 'does not set up default handlers' do respond_to expect(req).not_to be_ok expect(status).to eq(500) expect(body).to eq("Unknown template engine") end end describe "Helpers#respond_with" do describe "matching" do it 'allows defining handlers by file extensions' do respond_with(:ignore) do |format| format.html { "html!" } format.json { "json!" } end expect(req(:html).body).to eq("html!") expect(req(:json).body).to eq("json!") end it 'respects quality' do respond_with(:ignore) do |format| format.html { "html!" } format.json { "json!" } end expect(req("text/html;q=0.7, application/json;q=0.3").body).to eq("html!") expect(req("text/html;q=0.3, application/json;q=0.7").body).to eq("json!") end it 'allows using mime types' do respond_with(:ignore) do |format| format.on('text/html') { "html!" } format.json { "json!" } end expect(req(:html).body).to eq("html!") end it 'allows using wildcards in format matchers' do respond_with(:ignore) do |format| format.on('text/*') { "text!" } format.json { "json!" } end expect(req(:html).body).to eq("text!") end it 'allows using catch all wildcards in format matchers' do respond_with(:ignore) do |format| format.on('*/*') { "anything!" } format.json { "json!" } end expect(req(:html).body).to eq("anything!") end it 'prefers concret over generic' do respond_with(:ignore) do |format| format.on('text/*') { "text!" } format.on('*/*') { "anything!" } format.json { "json!" } end expect(req(:json).body).to eq("json!") expect(req(:html).body).to eq("text!") end end describe "default behavior" do it 'converts objects to json out of the box' do respond_with 'a' => 'b' expect(OkJson.decode(req(:json).body)).to eq({'a' => 'b'}) end it 'handles multiple routes correctly' do respond_app do get('/') { respond_with 'a' => 'b' } get('/:name') { respond_with 'a' => params[:name] } end expect(OkJson.decode(req('/', :json).body)).to eq({'a' => 'b'}) expect(OkJson.decode(req('/b', :json).body)).to eq({'a' => 'b'}) expect(OkJson.decode(req('/c', :json).body)).to eq({'a' => 'c'}) end it "calls to_EXT if available" do respond_with Struct.new(:to_pdf).new("hello") expect(req(:pdf).body).to eq("hello") end it 'results in a 500 if format cannot be produced' do respond_with({}) expect(req(:html)).not_to be_ok expect(status).to eq(500) expect(body).to eq("Unknown template engine") end end describe 'templates' do it 'looks for templates with name.target.engine' do respond_with :foo, :name => 'World' expect(req(:html)).to be_ok expect(body).to eq("Hello World!") end it 'looks for templates with name.engine for specific engines' do respond_with :bar expect(req(:html)).to be_ok expect(body).to eq("guten Tag!") end it 'does not use name.engine for engines producing other formats' do respond_with :not_html expect(req(:html)).not_to be_ok expect(status).to eq(500) expect(body).to eq("Unknown template engine") end it 'falls back to #json if no template is found' do respond_with :foo, :name => 'World' expect(req(:json)).to be_ok expect(OkJson.decode(body)).to eq({'name' => 'World'}) end it 'favors templates over #json' do respond_with :bar, :name => 'World' expect(req(:json)).to be_ok expect(body).to eq('json!') end it 'falls back to to_EXT if no template is found' do object = {:name => 'World'} def object.to_pdf; "hi" end respond_with :foo, object expect(req(:pdf)).to be_ok expect(body).to eq("hi") end unless defined? JRUBY_VERSION it 'uses yajl for json' do respond_with :baz expect(req(:json)).to be_ok expect(body).to eq("\"yajl!\"") end end end describe 'customizing' do it 'allows customizing' do respond_with(:foo, :name => 'World') { |f| f.html { 'html!' }} expect(req(:html)).to be_ok expect(body).to eq("html!") end it 'falls back to default behavior if none matches' do respond_with(:foo, :name => 'World') { |f| f.json { 'json!' }} expect(req(:html)).to be_ok expect(body).to eq("Hello World!") end it 'favors generic rule over default behavior' do respond_with(:foo, :name => 'World') { |f| f.on('*/*') { 'generic!' }} expect(req(:html)).to be_ok expect(body).to eq("generic!") end end describe "inherited" do it "registers RespondWith in an inherited app" do app = Sinatra.new do set :app_file, __FILE__ set :views, root + '/respond_with' register Sinatra::RespondWith get '/a' do respond_with :json end end self.app = Sinatra.new(app) expect(req('/a', :json)).not_to be_ok end end end describe :respond_to do it 'acts as global provides condition' do respond_app do respond_to :json, :html get('/a') { 'ok' } get('/b') { 'ok' } end expect(req('/b', :xml)).not_to be_ok expect(req('/b', :html)).to be_ok end it 'still allows provides' do respond_app do respond_to :json, :html get('/a') { 'ok' } get('/b', :provides => :json) { 'ok' } end expect(req('/b', :html)).not_to be_ok expect(req('/b', :json)).to be_ok end it 'plays well with namespaces' do respond_app do register Sinatra::Namespace namespace '/a' do respond_to :json get { 'json' } end get('/b') { 'anything' } end expect(req('/a', :html)).not_to be_ok expect(req('/b', :html)).to be_ok end end end sinatra-2.0.8.1/sinatra-contrib/spec/spec_helper.rb000066400000000000000000000002261360317524000221710ustar00rootroot00000000000000ENV['RACK_ENV'] = 'test' require 'sinatra/contrib' RSpec.configure do |config| config.expect_with :rspec config.include Sinatra::TestHelpers end sinatra-2.0.8.1/sinatra-contrib/spec/streaming_spec.rb000066400000000000000000000223271360317524000227110ustar00rootroot00000000000000require 'spec_helper' describe Sinatra::Streaming do def stream(&block) rack_middleware = @use out = nil mock_app do rack_middleware.each { |args| use(*args) } helpers Sinatra::Streaming get('/') { out = stream(&block) } end get('/') out end def use(*args) @use << args end before do @use = [] end context 'stream test helper' do it 'runs the given block' do ran = false stream { ran = true } expect(ran).to be true end it 'returns the stream object' do out = stream { } expect(out).to be_a(Sinatra::Helpers::Stream) end it 'fires a request against that stream' do stream { |out| out << "Hello World!" } expect(last_response).to be_ok expect(body).to eq("Hello World!") end it 'passes the stream object to the block' do passed = nil returned = stream { |out| passed = out } expect(passed).to eq(returned) end end context Sinatra::Streaming::Stream do it 'should extend the stream object' do out = stream { } expect(out).to be_a(Sinatra::Streaming::Stream) end it 'should not extend stream objects of other apps' do out = nil mock_app { get('/') { out = stream { }}} get('/') expect(out).to be_a(Sinatra::Helpers::Stream) expect(out).not_to be_a(Sinatra::Streaming::Stream) end end context 'app' do it 'is the app instance the stream was created from' do out = stream { } expect(out.app).to be_a(Sinatra::Base) end end context 'lineno' do it 'defaults to 0' do expect(stream { }.lineno).to eq(0) end it 'does not increase on write' do stream do |out| out << "many\nlines\n" expect(out.lineno).to eq(0) end end it 'is writable' do out = stream { } out.lineno = 10 expect(out.lineno).to eq(10) end end context 'pos' do it 'defaults to 0' do expect(stream { }.pos).to eq(0) end it 'increases when writing data' do stream do |out| expect(out.pos).to eq(0) out << 'hi' expect(out.pos).to eq(2) end end it 'is writable' do out = stream { } out.pos = 10 expect(out.pos).to eq(10) end it 'aliased to #tell' do out = stream { } expect(out.tell).to eq(0) out.pos = 10 expect(out.tell).to eq(10) end end context 'closed' do it 'returns false while streaming' do stream { |out| expect(out).not_to be_closed } end it 'returns true after streaming' do expect(stream {}).to be_closed end end context 'map!' do it 'applies transformations later' do stream do |out| out.map! { |s| s.upcase } out << 'ok' end expect(body).to eq("OK") end it 'is chainable' do stream do |out| out.map! { |s| s.upcase } out.map! { |s| s.reverse } out << 'ok' end expect(body).to eq("KO") end it 'works with middleware' do middleware = Class.new do def initialize(app) @app = app end def call(env) status, headers, body = @app.call(env) body.map! { |s| s.upcase } [status, headers, body] end end use middleware stream { |out| out << "ok" } expect(body).to eq("OK") end it 'modifies each value separately' do stream do |out| out.map! { |s| s.reverse } out << "ab" << "cd" end expect(body).to eq("badc") end end context 'map' do it 'works with middleware' do middleware = Class.new do def initialize(app) @app = app end def call(env) status, headers, body = @app.call(env) [status, headers, body.map(&:upcase)] end end use middleware stream { |out| out << "ok" } expect(body).to eq("OK") end it 'is chainable' do middleware = Class.new do def initialize(app) @app = app end def call(env) status, headers, body = @app.call(env) [status, headers, body.map(&:upcase).map(&:reverse)] end end use middleware stream { |out| out << "ok" } expect(body).to eq("KO") end it 'can be written as each.map' do middleware = Class.new do def initialize(app) @app = app end def call(env) status, headers, body = @app.call(env) [status, headers, body.each.map(&:upcase)] end end use middleware stream { |out| out << "ok" } expect(body).to eq("OK") end it 'does not modify the original body' do stream do |out| out.map { |s| s.reverse } out << 'ok' end expect(body).to eq('ok') end end context 'write' do it 'writes to the stream' do stream { |out| out.write 'hi' } expect(body).to eq('hi') end it 'returns the number of bytes' do stream do |out| expect(out.write('hi')).to eq(2) expect(out.write('hello')).to eq(5) end end it 'accepts non-string objects' do stream do |out| expect(out.write(12)).to eq(2) end end it 'should be aliased to syswrite' do stream { |out| expect(out.syswrite('hi')).to eq(2) } expect(body).to eq('hi') end it 'should be aliased to write_nonblock' do stream { |out| expect(out.write_nonblock('hi')).to eq(2) } expect(body).to eq('hi') end end context 'print' do it 'writes to the stream' do stream { |out| out.print('hi') } expect(body).to eq('hi') end it 'accepts multiple arguments' do stream { |out| out.print(1, 2, 3, 4) } expect(body).to eq('1234') end it 'returns nil' do stream { |out| expect(out.print('hi')).to be_nil } end end context 'printf' do it 'writes to the stream' do stream { |out| out.printf('hi') } expect(body).to eq('hi') end it 'interpolates the format string' do stream { |out| out.printf("%s: %d", "answer", 42) } expect(body).to eq('answer: 42') end it 'returns nil' do stream { |out| expect(out.printf('hi')).to be_nil } end end context 'putc' do it 'writes the first character of a string' do stream { |out| out.putc('hi') } expect(body).to eq('h') end it 'writes the character corresponding to an integer' do stream { |out| out.putc(42) } expect(body).to eq('*') end it 'returns nil' do stream { |out| expect(out.putc('hi')).to be_nil } end end context 'puts' do it 'writes to the stream' do stream { |out| out.puts('hi') } expect(body).to eq("hi\n") end it 'accepts multiple arguments' do stream { |out| out.puts(1, 2, 3, 4) } expect(body).to eq("1\n2\n3\n4\n") end it 'returns nil' do stream { |out| expect(out.puts('hi')).to be_nil } end end context 'close' do it 'sets #closed? to true' do stream do |out| out.close expect(out).to be_closed end end it 'sets #closed_write? to true' do stream do |out| expect(out).not_to be_closed_write out.close expect(out).to be_closed_write end end it 'fires callbacks' do stream do |out| fired = false out.callback { fired = true } out.close expect(fired).to be true end end it 'prevents from further writing' do stream do |out| out.close expect { out << 'hi' }.to raise_error(IOError, 'not opened for writing') end end end context 'close_read' do it 'raises the appropriate exception' do expect { stream { |out| out.close_read }}. to raise_error(IOError, "closing non-duplex IO for reading") end end context 'closed_read?' do it('returns true') { stream { |out| expect(out).to be_closed_read }} end context 'rewind' do it 'resets pos' do stream do |out| out << 'hi' out.rewind expect(out.pos).to eq(0) end end it 'resets lineno' do stream do |out| out.lineno = 10 out.rewind expect(out.lineno).to eq(0) end end end raises = %w[ bytes eof? eof getbyte getc gets read read_nonblock readbyte readchar readline readlines readpartial sysread ungetbyte ungetc ] enum = %w[chars each_line each_byte each_char lines] dummies = %w[flush fsync internal_encoding pid] raises.each do |method| context method do it 'raises the appropriate exception' do expect { stream { |out| out.public_send(method) }}. to raise_error(IOError, "not opened for reading") end end end enum.each do |method| context method do it 'creates an Enumerator' do stream { |out| expect(out.public_send(method)).to be_a(Enumerator) } end it 'calling each raises the appropriate exception' do expect { stream { |out| out.public_send(method).each { }}}. to raise_error(IOError, "not opened for reading") end end end dummies.each do |method| context method do it 'returns nil' do stream { |out| expect(out.public_send(method)).to be_nil } end end end end sinatra-2.0.8.1/sinatra.gemspec000066400000000000000000000042621360317524000163340ustar00rootroot00000000000000version = File.read(File.expand_path("../VERSION", __FILE__)).strip Gem::Specification.new 'sinatra', version do |s| s.description = "Sinatra is a DSL for quickly creating web applications in Ruby with minimal effort." s.summary = "Classy web-development dressed in a DSL" s.authors = ["Blake Mizerany", "Ryan Tomayko", "Simon Rozet", "Konstantin Haase"] s.email = "sinatrarb@googlegroups.com" s.homepage = "http://sinatrarb.com/" s.license = 'MIT' s.files = Dir['README*.md', 'lib/**/*', 'examples/*'] + [ ".yardopts", "AUTHORS.md", "CHANGELOG.md", "CONTRIBUTING.md", "Gemfile", "LICENSE", "MAINTENANCE.md", "Rakefile", "SECURITY.md", "sinatra.gemspec", "VERSION"] s.test_files = s.files.select { |p| p =~ /^test\/.*_test.rb/ } s.extra_rdoc_files = s.files.select { |p| p =~ /^README/ } << 'LICENSE' s.rdoc_options = %w[--line-numbers --inline-source --title Sinatra --main README.rdoc --encoding=UTF-8] if s.respond_to?(:metadata) s.metadata = { 'source_code_uri' => 'https://github.com/sinatra/sinatra', 'changelog_uri' => 'https://github.com/sinatra/sinatra/blob/master/CHANGELOG.md', 'homepage_uri' => 'http://sinatrarb.com/', 'bug_tracker_uri' => 'https://github.com/sinatra/sinatra/issues', 'mailing_list_uri' => 'http://groups.google.com/group/sinatrarb', 'documentation_uri' => 'https://www.rubydoc.info/gems/sinatra' } else msg = "RubyGems 2.0 or newer is required to protect against public "\ "gem pushes. You can update your rubygems version by running:\n\n"\ "gem install rubygems-update\n"\ "update_rubygems\n"\ "gem update --system" raise <<-EOF RubyGems 2.0 or newer is required to protect against public gem pushes. You can update your rubygems version by running: gem install rubygems-update update_rubygems: gem update --system EOF end s.required_ruby_version = '>= 2.2.0' s.add_dependency 'rack', '~> 2.0' s.add_dependency 'tilt', '~> 2.0' s.add_dependency 'rack-protection', version s.add_dependency 'mustermann', '~> 1.0' end sinatra-2.0.8.1/test/000077500000000000000000000000001360317524000143015ustar00rootroot00000000000000sinatra-2.0.8.1/test/asciidoctor_test.rb000066400000000000000000000036301360317524000201720ustar00rootroot00000000000000require File.expand_path('../helper', __FILE__) begin require 'asciidoctor' class AsciidoctorTest < Minitest::Test def asciidoc_app(&block) mock_app do set :views, File.dirname(__FILE__) + '/views' get('/', &block) end get '/' end it 'renders inline AsciiDoc strings' do asciidoc_app { asciidoc '== Hiya' } assert ok? assert_match %r{Hiya}, body end it 'uses the correct engine' do engine = Tilt::AsciidoctorTemplate assert_equal engine, Tilt[:ad] assert_equal engine, Tilt[:adoc] assert_equal engine, Tilt[:asciidoc] end it 'renders .asciidoc files in views path' do asciidoc_app { asciidoc :hello } assert ok? assert_match %r{Hello from AsciiDoc}, body end it 'raises error if template not found' do mock_app { get('/') { asciidoc :no_such_template } } assert_raises(Errno::ENOENT) { get('/') } end it 'renders with inline layouts' do mock_app do layout { 'THIS. IS. #{yield.upcase}!' } get('/') { asciidoc 'Sparta', :layout_engine => :str } end get '/' assert ok? assert_include body, 'THIS. IS.' assert_include body, '

SPARTA

' end it 'renders with file layouts' do asciidoc_app do asciidoc 'Hello World', :layout => :layout2, :layout_engine => :erb end assert ok? assert_include body, 'ERB Layout!' assert_include body, '

Hello World

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

.*
}m, body end end rescue LoadError warn "#{$!}: skipping asciidoc tests" end sinatra-2.0.8.1/test/base_test.rb000066400000000000000000000114461360317524000166050ustar00rootroot00000000000000require File.expand_path('../helper', __FILE__) class BaseTest < Minitest::Test describe 'Sinatra::Base subclasses' do class TestApp < Sinatra::Base get('/') { 'Hello World' } end it 'include Rack::Utils' do assert TestApp.included_modules.include?(Rack::Utils) end it 'processes requests with #call' do assert TestApp.respond_to?(:call) request = Rack::MockRequest.new(TestApp) response = request.get('/') assert response.ok? assert_equal 'Hello World', response.body end class TestApp < Sinatra::Base get '/state' do @foo ||= "new" body = "Foo: #{@foo}" @foo = 'discard' body end end it 'does not maintain state between requests' do request = Rack::MockRequest.new(TestApp) 2.times do response = request.get('/state') assert response.ok? assert_equal 'Foo: new', response.body end end it "passes the subclass to configure blocks" do ref = nil TestApp.configure { |app| ref = app } assert_equal TestApp, ref end it "allows the configure block arg to be omitted and does not change context" do context = nil TestApp.configure { context = self } assert_equal self, context end end describe "Sinatra::Base#new" do it 'returns a wrapper' do assert_equal Sinatra::Wrapper, Sinatra::Base.new.class end it 'implements a nice inspect' do assert_equal '#', Sinatra::Base.new.inspect end it 'exposes settings' do assert_equal Sinatra::Base.settings, Sinatra::Base.new.settings end it 'exposes helpers' do assert_equal 'image/jpeg', Sinatra::Base.new.helpers.mime_type(:jpg) end end describe "Sinatra::Base as Rack middleware" do app = lambda { |env| headers = {'X-Downstream' => 'true'} headers['X-Route-Missing'] = env['sinatra.route-missing'] || '' [210, headers, ['Hello from downstream']] } class TestMiddleware < Sinatra::Base end it 'creates a middleware that responds to #call with .new' do middleware = TestMiddleware.new(app) assert middleware.respond_to?(:call) end it 'exposes the downstream app' do middleware = TestMiddleware.new!(app) assert_same app, middleware.app end class TestMiddleware < Sinatra::Base def route_missing env['sinatra.route-missing'] = '1' super end get('/') { 'Hello from middleware' } end middleware = TestMiddleware.new(app) request = Rack::MockRequest.new(middleware) it 'intercepts requests' do response = request.get('/') assert response.ok? assert_equal 'Hello from middleware', response.body end it 'automatically forwards requests downstream when no matching route found' do response = request.get('/missing') assert_equal 210, response.status assert_equal 'Hello from downstream', response.body end it 'calls #route_missing before forwarding downstream' do response = request.get('/missing') assert_equal '1', response['X-Route-Missing'] end class TestMiddleware < Sinatra::Base get('/low-level-forward') { app.call(env) } end it 'can call the downstream app directly and return result' do response = request.get('/low-level-forward') assert_equal 210, response.status assert_equal 'true', response['X-Downstream'] assert_equal 'Hello from downstream', response.body end class TestMiddleware < Sinatra::Base get '/explicit-forward' do response['X-Middleware'] = 'true' res = forward assert_nil res assert_equal 210, response.status assert_equal 'true', response['X-Downstream'] assert_equal ['Hello from downstream'], response.body 'Hello after explicit forward' end end it 'forwards the request downstream and integrates the response into the current context' do response = request.get('/explicit-forward') assert_equal 210, response.status assert_equal 'true', response['X-Downstream'] assert_equal 'Hello after explicit forward', response.body assert_equal '28', response['Content-Length'] end app_content_length = lambda {|env| [200, {'Content-Length' => '16'}, 'From downstream!']} class TestMiddlewareContentLength < Sinatra::Base get '/forward' do 'From after explicit forward!' end end middleware_content_length = TestMiddlewareContentLength.new(app_content_length) request_content_length = Rack::MockRequest.new(middleware_content_length) it "sets content length for last response" do response = request_content_length.get('/forward') assert_equal '28', response['Content-Length'] end end end sinatra-2.0.8.1/test/builder_test.rb000066400000000000000000000043001360317524000173100ustar00rootroot00000000000000require File.expand_path('../helper', __FILE__) begin require 'builder' class BuilderTest < Minitest::Test def builder_app(options = {}, &block) mock_app do set :views, File.dirname(__FILE__) + '/views' set options get('/', &block) end get '/' end it 'renders inline Builder strings' do builder_app { builder 'xml.instruct!' } assert ok? assert_equal %{\n}, body end it 'defaults content type to xml' do builder_app { builder 'xml.instruct!' } assert ok? assert_equal "application/xml;charset=utf-8", response['Content-Type'] end it 'defaults allows setting content type per route' do builder_app do content_type :html builder 'xml.instruct!' end assert ok? assert_equal "text/html;charset=utf-8", response['Content-Type'] end it 'defaults allows setting content type globally' do builder_app(:builder => { :content_type => 'html' }) do builder 'xml.instruct!' end assert ok? assert_equal "text/html;charset=utf-8", response['Content-Type'] end it 'renders inline blocks' do builder_app do @name = "Frank & Mary" builder { |xml| xml.couple @name } end assert ok? assert_equal "Frank & Mary\n", body end it 'renders .builder files in views path' do builder_app do @name = "Blue" builder :hello end assert ok? assert_equal %(You're my boy, Blue!\n), body end it "renders with inline layouts" do mock_app do layout { %(xml.layout { xml << yield }) } get('/') { builder %(xml.em 'Hello World') } end get '/' assert ok? assert_equal "\nHello World\n\n", body end it "renders with file layouts" do builder_app do builder %(xml.em 'Hello World'), :layout => :layout2 end assert ok? assert_equal "\nHello World\n\n", body end it "raises error if template not found" do mock_app do get('/') { builder :no_such_template } end assert_raises(Errno::ENOENT) { get('/') } end end rescue LoadError warn "#{$!}: skipping builder tests" end sinatra-2.0.8.1/test/coffee_test.rb000066400000000000000000000043751360317524000171250ustar00rootroot00000000000000require File.expand_path('../helper', __FILE__) begin require 'coffee-script' require 'execjs' begin ExecJS.compile '1' rescue Exception raise LoadError, 'unable to execute JavaScript' end class CoffeeTest < Minitest::Test def coffee_app(options = {}, &block) mock_app do set :views, File.dirname(__FILE__) + '/views' set(options) get('/', &block) end get '/' end it 'renders inline Coffee strings' do coffee_app { coffee "alert 'Aye!'\n" } assert ok? assert body.include?("alert('Aye!');") end it 'defaults content type to javascript' do coffee_app { coffee "alert 'Aye!'\n" } assert ok? assert_equal "application/javascript;charset=utf-8", response['Content-Type'] end it 'defaults allows setting content type per route' do coffee_app do content_type :html coffee "alert 'Aye!'\n" end assert ok? assert_equal "text/html;charset=utf-8", response['Content-Type'] end it 'defaults allows setting content type globally' do coffee_app(:coffee => { :content_type => 'html' }) do coffee "alert 'Aye!'\n" end assert ok? assert_equal "text/html;charset=utf-8", response['Content-Type'] end it 'renders .coffee files in views path' do coffee_app { coffee :hello } assert ok? assert_include body, "alert(\"Aye!\");" end it 'ignores the layout option' do coffee_app { coffee :hello, :layout => :layout2 } assert ok? assert_include body, "alert(\"Aye!\");" end it "raises error if template not found" do mock_app { get('/') { coffee :no_such_template } } assert_raises(Errno::ENOENT) { get('/') } end it "passes coffee options to the coffee engine" do coffee_app { coffee "alert 'Aye!'\n", :no_wrap => true } assert ok? assert_body "alert('Aye!');" end it "passes default coffee options to the coffee engine" do mock_app do set :coffee, :no_wrap => true # default coffee style is :nested get('/') { coffee "alert 'Aye!'\n" } end get '/' assert ok? assert_body "alert('Aye!');" end end rescue LoadError warn "#{$!}: skipping coffee tests" rescue if $!.class.name == 'ExecJS::RuntimeUnavailable' warn "#{$!}: skipping coffee tests" else raise end end sinatra-2.0.8.1/test/compile_test.rb000066400000000000000000000142611360317524000173210ustar00rootroot00000000000000# I like coding: UTF-8 require File.expand_path('../helper', __FILE__) class CompileTest < Minitest::Test def self.parses pattern, example, expected_params, mtype = :sinatra, mopts = {} it "parses #{example} with #{pattern} into params #{expected_params}" do compiled = mock_app { set :mustermann_opts, :type => mtype }.send(:compile, pattern, mopts) params = compiled.params(example) fail %Q{"#{example}" does not parse on pattern "#{pattern}".} unless params assert_equal expected_params, params, "Pattern #{pattern} does not match path #{example}." end end def self.fails pattern, example, mtype = :sinatra, mopts = {} it "does not parse #{example} with #{pattern}" do compiled = mock_app { set :mustermann_opts, :type => mtype }.send(:compile, pattern, mopts) match = compiled.match(example) fail %Q{"#{pattern}" does parse "#{example}" but it should fail} if match end end def self.raises pattern, mtype = :sinatra, mopts = {} it "does not compile #{pattern}" do assert_raises(Mustermann::CompileError, %Q{Pattern "#{pattern}" compiles but it should not}) do mock_app { set :mustermann_opts, :type => mtype }.send(:compile, pattern, mopts) end end end parses "/", "/", {} parses "/foo", "/foo", {} parses "/:foo", "/foo", "foo" => "foo" parses "/:foo", "/foo.bar", "foo" => "foo.bar" parses "/:foo", "/foo%2Fbar", "foo" => "foo/bar" parses "/:foo", "/%0Afoo", "foo" => "\nfoo" fails "/:foo", "/foo?" fails "/:foo", "/foo/bar" fails "/:foo", "/" fails "/:foo", "/foo/" parses "/föö", "/f%C3%B6%C3%B6", {} parses "/:foo/:bar", "/foo/bar", "foo" => "foo", "bar" => "bar" parses "/hello/:person", "/hello/Frank", "person" => "Frank" parses "/?:foo?/?:bar?", "/hello/world", "foo" => "hello", "bar" => "world" parses "/?:foo?/?:bar?", "/hello", "foo" => "hello", "bar" => nil parses "/?:foo?/?:bar?", "/", "foo" => nil, "bar" => nil parses "/?:foo?/?:bar?", "", "foo" => nil, "bar" => nil parses "/*", "/", "splat" => [""] parses "/*", "/foo", "splat" => ["foo"] parses "/*", "/foo/bar", "splat" => ["foo/bar"] parses "/:foo/*", "/foo/bar/baz", "foo" => "foo", "splat" => ["bar/baz"] parses "/:foo/:bar", "/user@example.com/name", "foo" => "user@example.com", "bar" => "name" parses "/test$/", "/test$/", {} parses "/te+st/", "/te+st/", {} fails "/te+st/", "/test/" fails "/te+st/", "/teeest/" parses "/test(bar)/", "/testbar/", {} parses "/path with spaces", "/path%20with%20spaces", {} parses "/path with spaces", "/path%2Bwith%2Bspaces", {} parses "/path with spaces", "/path+with+spaces", {} parses "/foo&bar", "/foo&bar", {} parses "/:foo/*", "/hello%20world/how%20are%20you", "foo" => "hello world", "splat" => ["how are you"] parses "/*/foo/*/*", "/bar/foo/bling/baz/boom", "splat" => ["bar", "bling", "baz/boom"] parses "/*/foo/*/*rest", "/bar/foo/bling/baz/boom", "splat" => ["bar", "bling"], "rest" => "baz/boom" fails "/*/foo/*/*", "/bar/foo/baz" parses "/test.bar", "/test.bar", {} fails "/test.bar", "/test0bar" parses "/:file.:ext", "/pony.jpg", "file" => "pony", "ext" => "jpg" parses "/:file.:ext", "/pony%2Ejpg", "file" => "pony", "ext" => "jpg" fails "/:file.:ext", "/.jpg" parses "/:name.?:format?", "/foo", "name" => "foo", "format" => nil parses "/:name.?:format?", "/foo.bar", "name" => "foo", "format" => "bar" parses "/:name.?:format?", "/foo%2Ebar", "name" => "foo", "format" => "bar" parses "/:user@?:host?", "/foo@bar", "user" => "foo", "host" => "bar" parses "/:user@?:host?", "/foo.foo@bar", "user" => "foo.foo", "host" => "bar" parses "/:user@?:host?", "/foo@bar.bar", "user" => "foo", "host" => "bar.bar" # From https://gist.github.com/2154980#gistcomment-169469. # parses "/:name(.:format)?", "/foo", "name" => "foo", "format" => nil parses "/:name(.:format)?", "/foo.bar", "name" => "foo", "format" => "bar" parses "/:name(.:format)?", "/foo.", "name" => "foo.", "format" => nil parses "/:id/test.bar", "/3/test.bar", {"id" => "3"} parses "/:id/test.bar", "/2/test.bar", {"id" => "2"} parses "/:id/test.bar", "/2E/test.bar", {"id" => "2E"} parses "/:id/test.bar", "/2e/test.bar", {"id" => "2e"} parses "/:id/test.bar", "/%2E/test.bar", {"id" => "."} parses "/{id}/test.bar", "/%2E/test.bar", {"id" => "."} parses '/10/:id', '/10/test', "id" => "test" parses '/10/:id', '/10/te.st', "id" => "te.st" parses '/10.1/:id', '/10.1/test', "id" => "test" parses '/10.1/:id', '/10.1/te.st', "id" => "te.st" parses '/:foo/:id', '/10.1/te.st', "foo" => "10.1", "id" => "te.st" parses '/:foo/:id', '/10.1.2/te.st', "foo" => "10.1.2", "id" => "te.st" parses '/:foo.:bar/:id', '/10.1/te.st', "foo" => "10", "bar" => "1", "id" => "te.st" parses '/:a/:b.?:c?', '/a/b', "a" => "a", "b" => "b", "c" => nil parses '/:a/:b.?:c?', '/a/b.c', "a" => "a", "b" => "b", "c" => "c" parses '/:a/:b.?:c?', '/a.b/c', "a" => "a.b", "b" => "c", "c" => nil parses '/:a/:b.?:c?', '/a.b/c.d', "a" => "a.b", "b" => "c", "c" => "d" fails '/:a/:b.?:c?', '/a.b/c.d/e' parses "/:file.:ext", "/pony%2ejpg", "file" => "pony", "ext" => "jpg" parses "/:file.:ext", "/pony%E6%AD%A3%2Ejpg", "file" => "pony正", "ext" => "jpg" parses "/:file.:ext", "/pony%e6%ad%a3%2ejpg", "file" => "pony正", "ext" => "jpg" parses "/:file.:ext", "/pony正%2Ejpg", "file" => "pony正", "ext" => "jpg" parses "/:file.:ext", "/pony正%2ejpg", "file" => "pony正", "ext" => "jpg" parses "/:file.:ext", "/pony正..jpg", "file" => "pony正.", "ext" => "jpg" parses "/:name.:format", "/file.tar.gz", "name" => "file.tar", "format" => "gz" parses "/:name.:format1.:format2", "/file.tar.gz", "name" => "file", "format1" => "tar", "format2" => "gz" parses "/:name.:format1.:format2", "/file.temp.tar.gz", "name" => "file.temp", "format1" => "tar", "format2" => "gz" # From issue #688. # parses "/articles/10.1103/:doi", "/articles/10.1103/PhysRevLett.110.026401", "doi" => "PhysRevLett.110.026401" # Mustermann anchoring fails "/bar", "/foo/bar", :regexp raises "^/foo/bar$", :regexp parses "^/foo/bar$", "/foo/bar", {}, :regexp, :check_anchors => false end sinatra-2.0.8.1/test/contest.rb000066400000000000000000000062451360317524000163140ustar00rootroot00000000000000# Copyright (c) 2009 Damian Janowski and Michel Martens for Citrusbyte # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require "rubygems" require "minitest/autorun" # Contest adds +teardown+, +test+ and +context+ as class methods, and the # instance methods +setup+ and +teardown+ now iterate on the corresponding # blocks. Note that all setup and teardown blocks must be defined with the # block syntax. Adding setup or teardown instance methods defeats the purpose # of this library. class Minitest::Test def self.setup(&block) setup_blocks << block end def self.teardown(&block) teardown_blocks << block end def self.setup_blocks() @setup_blocks ||= [] end def self.teardown_blocks() @teardown_blocks ||= [] end def setup_blocks(base = self.class) setup_blocks base.superclass if base.superclass.respond_to? :setup_blocks base.setup_blocks.each do |block| instance_eval(&block) end end def teardown_blocks(base = self.class) teardown_blocks base.superclass if base.superclass.respond_to? :teardown_blocks base.teardown_blocks.each do |block| instance_eval(&block) end end alias setup setup_blocks alias teardown teardown_blocks def self.context(*name, &block) subclass = Class.new(self) remove_tests(subclass) subclass.class_eval(&block) if block_given? const_set(context_name(name.join(" ")), subclass) end def self.test(name, &block) define_method(test_name(name), &block) end class << self alias_method :should, :test alias_method :describe, :context end private def self.context_name(name) # "Test#{sanitize_name(name).gsub(/(^| )(\w)/) { $2.upcase }}".to_sym name = "Test#{sanitize_name(name).gsub(/(^| )(\w)/) { $2.upcase }}" name.tr(" ", "_").to_sym end def self.test_name(name) name = "test_#{sanitize_name(name).gsub(/\s+/,'_')}_0" name = name.succ while method_defined? name name.to_sym end def self.sanitize_name(name) # name.gsub(/\W+/, ' ').strip name.gsub(/\W+/, ' ') end def self.remove_tests(subclass) subclass.public_instance_methods.grep(/^test_/).each do |meth| subclass.send(:undef_method, meth.to_sym) end end end sinatra-2.0.8.1/test/creole_test.rb000066400000000000000000000027331360317524000171430ustar00rootroot00000000000000require File.expand_path('../helper', __FILE__) begin require 'creole' class CreoleTest < Minitest::Test def creole_app(&block) mock_app do set :views, File.dirname(__FILE__) + '/views' get('/', &block) end get '/' end it 'renders inline creole strings' do creole_app { creole '= Hiya' } assert ok? assert_body "

Hiya

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

Hello From Creole

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

SPARTA

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

Hello World

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

hi

', body end end rescue LoadError warn "#{$!}: skipping creole tests" end sinatra-2.0.8.1/test/delegator_test.rb000066400000000000000000000074231360317524000176410ustar00rootroot00000000000000require File.expand_path('../helper', __FILE__) class DelegatorTest < Minitest::Test class Mirror attr_reader :last_call def method_missing(*a, &b) @last_call = [*a.map(&:to_s)] @last_call << b if b end end def self.delegates(name) it "delegates #{name}" do m = mirror { send name } assert_equal [name.to_s], m.last_call end it "delegates #{name} with arguments" do m = mirror { send name, "foo", "bar" } assert_equal [name.to_s, "foo", "bar"], m.last_call end it "delegates #{name} with block" do block = proc { } m = mirror { send(name, &block) } assert_equal [name.to_s, block], m.last_call end end setup do @target_was = Sinatra::Delegator.target end def teardown Sinatra::Delegator.target = @target_was end def delegation_app(&block) mock_app { Sinatra::Delegator.target = self } delegate(&block) end def mirror(&block) mirror = Mirror.new Sinatra::Delegator.target = mirror delegate(&block) end def delegate(&block) assert Sinatra::Delegator.target != Sinatra::Application Object.new.extend(Sinatra::Delegator).instance_eval(&block) if block Sinatra::Delegator.target end def target Sinatra::Delegator.target end it 'defaults to Sinatra::Application as target' do assert_equal Sinatra::Application, Sinatra::Delegator.target end %w[get put post delete options patch link unlink].each do |verb| it "delegates #{verb} correctly" do delegation_app do send(verb, '/hello') { 'Hello World' } end request = Rack::MockRequest.new(@app) response = request.request(verb.upcase, '/hello', {}) assert response.ok? assert_equal 'Hello World', response.body end end it "delegates head correctly" do delegation_app do head '/hello' do response['X-Hello'] = 'World!' 'remove me' end end request = Rack::MockRequest.new(@app) response = request.request('HEAD', '/hello', {}) assert response.ok? assert_equal 'World!', response['X-Hello'] assert_equal '', response.body end it "registers extensions with the delegation target" do app, mixin = mirror, Module.new Sinatra.register mixin assert_equal ["register", mixin.to_s], app.last_call end it "registers helpers with the delegation target" do app, mixin = mirror, Module.new Sinatra.helpers mixin assert_equal ["helpers", mixin.to_s], app.last_call end it "registers middleware with the delegation target" do app, mixin = mirror, Module.new Sinatra.use mixin assert_equal ["use", mixin.to_s], app.last_call end it "should work with method_missing proxies for options" do mixin = Module.new do def respond_to?(method, *) method.to_sym == :options or super end def method_missing(method, *args, &block) return super unless method.to_sym == :options {:some => :option} end end value = nil mirror do extend mixin value = options end assert_equal({:some => :option}, value) end it "delegates crazy method names" do Sinatra::Delegator.delegate "foo:bar:" method = mirror { send "foo:bar:" }.last_call.first assert_equal "foo:bar:", method end delegates 'get' delegates 'patch' delegates 'put' delegates 'post' delegates 'delete' delegates 'head' delegates 'options' delegates 'template' delegates 'layout' delegates 'before' delegates 'after' delegates 'error' delegates 'not_found' delegates 'configure' delegates 'set' delegates 'mime_type' delegates 'enable' delegates 'disable' delegates 'use' delegates 'development?' delegates 'test?' delegates 'production?' delegates 'helpers' delegates 'settings' end sinatra-2.0.8.1/test/encoding_test.rb000066400000000000000000000011531360317524000174530ustar00rootroot00000000000000# encoding: UTF-8 require File.expand_path('../helper', __FILE__) require 'erb' class BaseTest < Minitest::Test setup do @base = Sinatra.new(Sinatra::Base) @base.set :views, File.dirname(__FILE__) + "/views" end it 'allows unicode strings in ascii templates per default (1.9)' do next unless defined? Encoding @base.new!.erb(File.read(@base.views + "/ascii.erb").encode("ASCII"), {}, :value => "åkej") end it 'allows ascii strings in unicode templates per default (1.9)' do next unless defined? Encoding @base.new!.erb(:utf8, {}, :value => "Some Lyrics".encode("ASCII")) end end sinatra-2.0.8.1/test/erb_test.rb000066400000000000000000000053141360317524000164400ustar00rootroot00000000000000require File.expand_path('../helper', __FILE__) class ERBTest < Minitest::Test def engine Tilt::ERBTemplate end def setup Tilt.prefer engine, :erb super end def erb_app(&block) mock_app do set :views, File.dirname(__FILE__) + '/views' get('/', &block) end get '/' end it 'uses the correct engine' do assert_equal engine, Tilt[:erb] end it 'renders inline ERB strings' do erb_app { erb '<%= 1 + 1 %>' } assert ok? assert_equal '2', body end it 'renders .erb files in views path' do erb_app { erb :hello } assert ok? assert_equal "Hello World\n", body end it 'takes a :locals option' do erb_app do locals = {:foo => 'Bar'} erb '<%= foo %>', :locals => locals end assert ok? assert_equal 'Bar', body end it "renders with inline layouts" do mock_app do layout { 'THIS. IS. <%= yield.upcase %>!' } get('/') { erb 'Sparta' } end get '/' assert ok? assert_equal 'THIS. IS. SPARTA!', body end it "renders with file layouts" do erb_app { erb 'Hello World', :layout => :layout2 } assert ok? assert_body "ERB Layout!\nHello World" end it "renders erb with blocks" do mock_app do def container @_out_buf << "THIS." yield @_out_buf << "SPARTA!" end def is; "IS." end get('/') { erb '<% container do %> <%= is %> <% end %>' } end get '/' assert ok? assert_equal 'THIS. IS. SPARTA!', body end it "can be used in a nested fashion for partials and whatnot" do mock_app do template(:inner) { "<%= 'hi' %>" } template(:outer) { "<%= erb :inner %>" } get('/') { erb :outer } end get '/' assert ok? assert_equal 'hi', body end it "can render truly nested layouts by accepting a layout and a block with the contents" do mock_app do template(:main_outer_layout) { "

Title

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

Subtitle

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

Contents.

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

Title

\n

Subtitle

\n

Contents.

\n" end end begin require 'erubi' class ErubiTest < ERBTest def engine; Tilt::ErubiTemplate end end rescue LoadError warn "#{$!}: skipping erubi tests" end begin require 'erubis' class ErubisTest < ERBTest def engine; Tilt::ErubisTemplate end end rescue LoadError warn "#{$!}: skipping erubis tests" end sinatra-2.0.8.1/test/extensions_test.rb000066400000000000000000000053021360317524000200640ustar00rootroot00000000000000require File.expand_path('../helper', __FILE__) class ExtensionsTest < Minitest::Test module FooExtensions def foo end private def im_hiding_in_ur_foos end end module BarExtensions def bar end end module BazExtensions def baz end end module QuuxExtensions def quux end end module PainExtensions def foo=(name); end def bar?(name); end def fizz!(name); end end it 'will add the methods to the DSL for the class in which you register them and its subclasses' do Sinatra::Base.register FooExtensions assert Sinatra::Base.respond_to?(:foo) Sinatra::Application.register BarExtensions assert Sinatra::Application.respond_to?(:bar) assert Sinatra::Application.respond_to?(:foo) assert !Sinatra::Base.respond_to?(:bar) end it 'allows extending by passing a block' do Sinatra::Base.register { def im_in_ur_anonymous_module; end } assert Sinatra::Base.respond_to?(:im_in_ur_anonymous_module) end it 'will make sure any public methods added via Application#register are delegated to Sinatra::Delegator' do Sinatra::Application.register FooExtensions assert Sinatra::Delegator.private_instance_methods. map { |m| m.to_sym }.include?(:foo) assert !Sinatra::Delegator.private_instance_methods. map { |m| m.to_sym }.include?(:im_hiding_in_ur_foos) end it 'will handle special method names' do Sinatra::Application.register PainExtensions assert Sinatra::Delegator.private_instance_methods. map { |m| m.to_sym }.include?(:foo=) assert Sinatra::Delegator.private_instance_methods. map { |m| m.to_sym }.include?(:bar?) assert Sinatra::Delegator.private_instance_methods. map { |m| m.to_sym }.include?(:fizz!) end it 'will not delegate methods on Base#register' do Sinatra::Base.register QuuxExtensions assert !Sinatra::Delegator.private_instance_methods.include?("quux") end it 'will extend the Sinatra::Application application by default' do Sinatra.register BazExtensions assert !Sinatra::Base.respond_to?(:baz) assert Sinatra::Application.respond_to?(:baz) end module BizzleExtension def bizzle bizzle_option end def self.registered(base) fail "base should be BizzleApp" unless base == BizzleApp fail "base should have already extended BizzleExtension" unless base.respond_to?(:bizzle) base.set :bizzle_option, 'bizzle!' end end class BizzleApp < Sinatra::Base end it 'sends .registered to the extension module after extending the class' do BizzleApp.register BizzleExtension assert_equal 'bizzle!', BizzleApp.bizzle_option assert_equal 'bizzle!', BizzleApp.bizzle end end sinatra-2.0.8.1/test/filter_test.rb000066400000000000000000000263751360317524000171670ustar00rootroot00000000000000require File.expand_path('../helper', __FILE__) class BeforeFilterTest < Minitest::Test it "executes filters in the order defined" do count = 0 mock_app do get('/') { 'Hello World' } before do assert_equal 0, count count = 1 end before do assert_equal 1, count count = 2 end end get '/' assert ok? assert_equal 2, count assert_equal 'Hello World', body end it "can modify the request" do mock_app do get('/foo') { 'foo' } get('/bar') { 'bar' } before { request.path_info = '/bar' } end get '/foo' assert ok? assert_equal 'bar', body end it "can modify instance variables available to routes" do mock_app do before { @foo = 'bar' } get('/foo') { @foo } end get '/foo' assert ok? assert_equal 'bar', body end it "allows redirects" do mock_app do before { redirect '/bar' } get('/foo') do fail 'before block should have halted processing' 'ORLY?!' end end get '/foo' assert redirect? assert_equal 'http://example.org/bar', response['Location'] assert_equal '', body end it "does not modify the response with its return value" do mock_app do before { 'Hello World!' } get('/foo') do assert_equal [], response.body 'cool' end end get '/foo' assert ok? assert_equal 'cool', body end it "does modify the response with halt" do mock_app do before { halt 302, 'Hi' } get '/foo' do "should not happen" end end get '/foo' assert_equal 302, response.status assert_equal 'Hi', body end it "gives you access to params" do mock_app do before { @foo = params['foo'] } get('/foo') { @foo } end get '/foo?foo=cool' assert ok? assert_equal 'cool', body end it "properly unescapes parameters" do mock_app do before { @foo = params['foo'] } get('/foo') { @foo } end get '/foo?foo=bar%3Abaz%2Fbend' assert ok? assert_equal 'bar:baz/bend', body end it "runs filters defined in superclasses" do base = Class.new(Sinatra::Base) base.before { @foo = 'hello from superclass' } mock_app(base) { get('/foo') { @foo } } get '/foo' assert_equal 'hello from superclass', body end it 'does not run before filter when serving static files' do ran_filter = false mock_app do before { ran_filter = true } set :static, true set :public_folder, File.dirname(__FILE__) end get "/#{File.basename(__FILE__)}" assert ok? assert_equal File.read(__FILE__), body assert !ran_filter end it 'takes an optional route pattern' do ran_filter = false mock_app do before("/b*") { ran_filter = true } get('/foo') { } get('/bar') { } end get '/foo' assert !ran_filter get '/bar' assert ran_filter end it 'generates block arguments from route pattern' do subpath = nil mock_app do before("/foo/:sub") { |s| subpath = s } get('/foo/*') { } end get '/foo/bar' assert_equal subpath, 'bar' end it 'can catch exceptions in before filters and handle them properly' do doodle = '' mock_app do before do doodle += 'This begins' raise StandardError, "before" end get "/" do doodle = 'and runs' end error 500 do "Error handled #{env['sinatra.error'].message}" end end doodle = '' get '/' assert_equal 'Error handled before', body assert_equal 'This begins', doodle end end class AfterFilterTest < Minitest::Test it "executes before and after filters in correct order" do invoked = 0 mock_app do before { invoked = 2 } get('/') { invoked += 2; 'hello' } after { invoked *= 2 } end get '/' assert ok? assert_equal 8, invoked end it "executes filters in the order defined" do count = 0 mock_app do get('/') { 'Hello World' } after do assert_equal 0, count count = 1 end after do assert_equal 1, count count = 2 end end get '/' assert ok? assert_equal 2, count assert_equal 'Hello World', body end it "allows redirects" do mock_app do get('/foo') { 'ORLY' } after { redirect '/bar' } end get '/foo' assert redirect? assert_equal 'http://example.org/bar', response['Location'] assert_equal '', body end it "does not modify the response with its return value" do mock_app do get('/foo') { 'cool' } after { 'Hello World!' } end get '/foo' assert ok? assert_equal 'cool', body end it "does modify the response with halt" do mock_app do get '/foo' do "should not be returned" end after { halt 302, 'Hi' } end get '/foo' assert_equal 302, response.status assert_equal 'Hi', body end it "runs filters defined in superclasses" do count = 2 base = Class.new(Sinatra::Base) base.after { count *= 2 } mock_app(base) do get('/foo') do count += 2 "ok" end end get '/foo' assert_equal 8, count end it 'does not run after filter when serving static files' do ran_filter = false mock_app do after { ran_filter = true } set :static, true set :public_folder, File.dirname(__FILE__) end get "/#{File.basename(__FILE__)}" assert ok? assert_equal File.read(__FILE__), body assert !ran_filter end it 'takes an optional route pattern' do ran_filter = false mock_app do after("/b*") { ran_filter = true } get('/foo') { } get('/bar') { } end get '/foo' assert !ran_filter get '/bar' assert ran_filter end it 'changes to path_info from a pattern matching before filter are respected when routing' do mock_app do before('/foo') { request.path_info = '/bar' } get('/bar') { 'blah' } end get '/foo' assert ok? assert_equal 'blah', body end it 'generates block arguments from route pattern' do subpath = nil mock_app do after("/foo/:sub") { |s| subpath = s } get('/foo/*') { } end get '/foo/bar' assert_equal subpath, 'bar' end it 'is possible to access url params from the route param' do ran = false mock_app do get('/foo/*') { } before('/foo/:sub') do assert_equal params[:sub], 'bar' ran = true end end get '/foo/bar' assert ran end it 'is possible to apply host_name conditions to before filters with no path' do ran = false mock_app do before(:host_name => 'example.com') { ran = true } get('/') { 'welcome' } end get('/', {}, { 'HTTP_HOST' => 'example.org' }) assert !ran get('/', {}, { 'HTTP_HOST' => 'example.com' }) assert ran end it 'is possible to apply host_name conditions to before filters with a path' do ran = false mock_app do before('/foo', :host_name => 'example.com') { ran = true } get('/') { 'welcome' } end get('/', {}, { 'HTTP_HOST' => 'example.com' }) assert !ran get('/foo', {}, { 'HTTP_HOST' => 'example.org' }) assert !ran get('/foo', {}, { 'HTTP_HOST' => 'example.com' }) assert ran end it 'is possible to apply host_name conditions to after filters with no path' do ran = false mock_app do after(:host_name => 'example.com') { ran = true } get('/') { 'welcome' } end get('/', {}, { 'HTTP_HOST' => 'example.org' }) assert !ran get('/', {}, { 'HTTP_HOST' => 'example.com' }) assert ran end it 'is possible to apply host_name conditions to after filters with a path' do ran = false mock_app do after('/foo', :host_name => 'example.com') { ran = true } get('/') { 'welcome' } end get('/', {}, { 'HTTP_HOST' => 'example.com' }) assert !ran get('/foo', {}, { 'HTTP_HOST' => 'example.org' }) assert !ran get('/foo', {}, { 'HTTP_HOST' => 'example.com' }) assert ran end it 'is possible to apply user_agent conditions to before filters with no path' do ran = false mock_app do before(:user_agent => /foo/) { ran = true } get('/') { 'welcome' } end get('/', {}, { 'HTTP_USER_AGENT' => 'bar' }) assert !ran get('/', {}, { 'HTTP_USER_AGENT' => 'foo' }) assert ran end it 'is possible to apply user_agent conditions to before filters with a path' do ran = false mock_app do before('/foo', :user_agent => /foo/) { ran = true } get('/') { 'welcome' } end get('/', {}, { 'HTTP_USER_AGENT' => 'foo' }) assert !ran get('/foo', {}, { 'HTTP_USER_AGENT' => 'bar' }) assert !ran get('/foo', {}, { 'HTTP_USER_AGENT' => 'foo' }) assert ran end it 'can add params' do mock_app do before { params['foo'] = 'bar' } get('/') { params['foo'] } end get '/' assert_body 'bar' end it 'can add params on a single path' do mock_app do before('/hi'){ params['foo'] = 'bar' } get('/hi') { params['foo'] } end get '/hi' assert_body 'bar' end # ref: issue #1567 it 'can add params on named parameters path' do mock_app do before('/:id/hi'){ params['foo'] = 'bar' } get('/:id/hi') { params['foo'] } end get '/:id/hi' assert_body 'bar' end it 'can remove params' do mock_app do before { params.delete('foo') } get('/') { params['foo'].to_s } end get '/?foo=bar' assert_body '' end it 'is possible to apply user_agent conditions to after filters with no path' do ran = false mock_app do after(:user_agent => /foo/) { ran = true } get('/') { 'welcome' } end get('/', {}, { 'HTTP_USER_AGENT' => 'bar' }) assert !ran get('/', {}, { 'HTTP_USER_AGENT' => 'foo' }) assert ran end it 'is possible to apply user_agent conditions to after filters with a path' do ran = false mock_app do after('/foo', :user_agent => /foo/) { ran = true } get('/') { 'welcome' } end get('/', {}, { 'HTTP_USER_AGENT' => 'foo' }) assert !ran get('/foo', {}, { 'HTTP_USER_AGENT' => 'bar' }) assert !ran get('/foo', {}, { 'HTTP_USER_AGENT' => 'foo' }) assert ran end it 'only triggers provides condition if conforms with current Content-Type' do mock_app do before(:provides => :txt) { @type = 'txt' } before(:provides => :html) { @type = 'html' } get('/') { @type } end get('/', {}, { 'HTTP_ACCEPT' => '*/*' }) assert_body 'txt' end it 'can catch exceptions in after filters and handle them properly' do doodle = '' mock_app do after do doodle += ' and after' raise StandardError, "after" end get "/foo" do doodle = 'Been now' raise StandardError, "now" end get "/" do doodle = 'Been now' end error 500 do "Error handled #{env['sinatra.error'].message}" end end get '/foo' assert_equal 'Error handled now', body assert_equal 'Been now and after', doodle doodle = '' get '/' assert_equal 'Error handled after', body assert_equal 'Been now and after', doodle end end sinatra-2.0.8.1/test/haml_test.rb000066400000000000000000000056331360317524000166150ustar00rootroot00000000000000require File.expand_path('../helper', __FILE__) begin require 'haml' class HAMLTest < Minitest::Test def haml_app(&block) mock_app do set :views, File.dirname(__FILE__) + '/views' get('/', &block) end get '/' end it 'renders inline HAML strings' do haml_app { haml '%h1 Hiya' } assert ok? assert_equal "

Hiya

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

Hello From Haml

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

THIS. IS. SPARTA

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

HAML Layout!

\n

Hello World

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

Hello World

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

Hello World

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

Hello World

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

Title

\n

Subtitle

\n

Contents.

\n" end end rescue LoadError warn "#{$!}: skipping haml tests" end sinatra-2.0.8.1/test/helper.rb000066400000000000000000000072321360317524000161110ustar00rootroot00000000000000if ENV['COVERAGE'] require 'simplecov' SimpleCov.start do add_filter '/test/' add_group 'sinatra-contrib', 'sinatra-contrib' add_group 'rack-protection', 'rack-protection' end end ENV['APP_ENV'] = 'test' Encoding.default_external = "UTF-8" if defined? Encoding RUBY_ENGINE = 'ruby' unless defined? RUBY_ENGINE require 'rack' testdir = File.dirname(__FILE__) $LOAD_PATH.unshift testdir unless $LOAD_PATH.include?(testdir) libdir = File.dirname(File.dirname(__FILE__)) + '/lib' $LOAD_PATH.unshift libdir unless $LOAD_PATH.include?(libdir) require 'minitest' require 'contest' require 'rack/test' # Some of ActiveSupport's core extensions to Hash get loaded during internal # testing (e.g. by RABL and our RABL test) that we have no control over, but we # need them to load *before* Sinatra::IndifferentHash (which is itself loaded # by Sinatra::Base) whenever the full test suite is executed, so we'll do it # preemptively here. # # Newer Rubies have these methods built-in, so the extensions are no-ops. require 'active_support/core_ext/hash/conversions' require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/hash/keys' require 'sinatra/base' class Sinatra::Base include Minitest::Assertions # Allow assertions in request context def assertions @assertions ||= 0 end attr_writer :assertions end class Rack::Builder def include?(middleware) @ins.any? { |m| middleware === m } end end Sinatra::Base.set :environment, :test class Minitest::Test include Rack::Test::Methods class << self alias_method :it, :test alias_method :section, :context end def self.example(desc = nil, &block) @example_count = 0 unless instance_variable_defined? :@example_count @example_count += 1 it(desc || "Example #{@example_count}", &block) end alias_method :response, :last_response setup do Sinatra::Base.set :environment, :test end # Sets up a Sinatra::Base subclass defined with the block # given. Used in setup or individual spec methods to establish # the application. def mock_app(base=Sinatra::Base, &block) @app = Sinatra.new(base, &block) end def app Rack::Lint.new(@app) end def body response.body.to_s end def assert_body(value) if value.respond_to? :to_str assert_equal value.lstrip.gsub(/\s*\n\s*/, ""), body.lstrip.gsub(/\s*\n\s*/, "") else assert_match value, body end end def assert_status(expected) assert_equal Integer(expected), Integer(status) end def assert_like(a,b) pattern = /id=['"][^"']*["']|\s+/ assert_equal a.strip.gsub(pattern, ""), b.strip.gsub(pattern, "") end def assert_include(str, substr) assert str.include?(substr), "expected #{str.inspect} to include #{substr.inspect}" end def options(uri, params = {}, env = {}, &block) request(uri, env.merge(:method => "OPTIONS", :params => params), &block) end def patch(uri, params = {}, env = {}, &block) request(uri, env.merge(:method => "PATCH", :params => params), &block) end def link(uri, params = {}, env = {}, &block) request(uri, env.merge(:method => "LINK", :params => params), &block) end def unlink(uri, params = {}, env = {}, &block) request(uri, env.merge(:method => "UNLINK", :params => params), &block) end # Delegate other missing methods to response. def method_missing(name, *args, &block) if response && response.respond_to?(name) response.send(name, *args, &block) else super end rescue Rack::Test::Error super end # Do not output warnings for the duration of the block. def silence_warnings $VERBOSE, v = nil, $VERBOSE yield ensure $VERBOSE = v end end sinatra-2.0.8.1/test/helpers_test.rb000066400000000000000000001405511360317524000173350ustar00rootroot00000000000000require File.expand_path('../helper', __FILE__) require 'date' require 'json' class HelpersTest < Minitest::Test def test_default assert true end def status_app(code, &block) code += 2 if [204, 304].include? code block ||= proc { } mock_app do get('/') do status code instance_eval(&block).inspect end end get '/' end describe 'status' do it 'sets the response status code' do status_app 207 assert_equal 207, response.status end end describe 'bad_request?' do it 'is true for status == 400' do status_app(400) { bad_request? } assert_body 'true' end it 'is false for status gt 400' do status_app(401) { bad_request? } assert_body 'false' end it 'is false for status lt 400' do status_app(399) { bad_request? } assert_body 'false' end end describe 'not_found?' do it 'is true for status == 404' do status_app(404) { not_found? } assert_body 'true' end it 'is false for status gt 404' do status_app(405) { not_found? } assert_body 'false' end it 'is false for status lt 404' do status_app(403) { not_found? } assert_body 'false' end end describe 'informational?' do it 'is true for 1xx status' do status_app(100 + rand(100)) { informational? } assert_body 'true' end it 'is false for status > 199' do status_app(200 + rand(400)) { informational? } assert_body 'false' end end describe 'success?' do it 'is true for 2xx status' do status_app(200 + rand(100)) { success? } assert_body 'true' end it 'is false for status < 200' do status_app(100 + rand(100)) { success? } assert_body 'false' end it 'is false for status > 299' do status_app(300 + rand(300)) { success? } assert_body 'false' end end describe 'redirect?' do it 'is true for 3xx status' do status_app(300 + rand(100)) { redirect? } assert_body 'true' end it 'is false for status < 300' do status_app(200 + rand(100)) { redirect? } assert_body 'false' end it 'is false for status > 399' do status_app(400 + rand(200)) { redirect? } assert_body 'false' end end describe 'client_error?' do it 'is true for 4xx status' do status_app(400 + rand(100)) { client_error? } assert_body 'true' end it 'is false for status < 400' do status_app(200 + rand(200)) { client_error? } assert_body 'false' end it 'is false for status > 499' do status_app(500 + rand(100)) { client_error? } assert_body 'false' end end describe 'server_error?' do it 'is true for 5xx status' do status_app(500 + rand(100)) { server_error? } assert_body 'true' end it 'is false for status < 500' do status_app(200 + rand(300)) { server_error? } assert_body 'false' end end describe 'body' do it 'takes a block for deferred body generation' do mock_app do get('/') { body { 'Hello World' } } end get '/' assert_equal 'Hello World', body end it 'takes a String, Array, or other object responding to #each' do mock_app { get('/') { body 'Hello World' } } get '/' assert_equal 'Hello World', body end it 'can be used with other objects' do mock_app do get '/' do body :hello => 'from json' end after do if Hash === response.body body response.body[:hello] end end end get '/' assert_body 'from json' end it 'can be set in after filter' do mock_app do get('/') { body 'route' } after { body 'filter' } end get '/' assert_body 'filter' end end describe 'redirect' do it 'uses a 302 when only a path is given' do mock_app do get('/') do redirect '/foo' fail 'redirect should halt' end end get '/' assert_equal 302, status assert_equal '', body assert_equal 'http://example.org/foo', response['Location'] end it 'uses the code given when specified' do mock_app do get('/') do redirect '/foo', 301 fail 'redirect should halt' end end get '/' assert_equal 301, status assert_equal '', body assert_equal 'http://example.org/foo', response['Location'] end it 'redirects back to request.referer when passed back' do mock_app { get('/try_redirect') { redirect back } } request = Rack::MockRequest.new(@app) response = request.get('/try_redirect', 'HTTP_REFERER' => '/foo') assert_equal 302, response.status assert_equal 'http://example.org/foo', response['Location'] end it 'redirects using a non-standard HTTP port' do mock_app { get('/') { redirect '/foo' } } request = Rack::MockRequest.new(@app) response = request.get('/', 'SERVER_PORT' => '81') assert_equal 'http://example.org:81/foo', response['Location'] end it 'redirects using a non-standard HTTPS port' do mock_app { get('/') { redirect '/foo' } } request = Rack::MockRequest.new(@app) response = request.get('/', 'SERVER_PORT' => '444') assert_equal 'http://example.org:444/foo', response['Location'] end it 'uses 303 for post requests if request is HTTP 1.1' do mock_app { post('/') { redirect '/'} } post('/', {}, 'HTTP_VERSION' => 'HTTP/1.1') assert_equal 303, status assert_equal '', body assert_equal 'http://example.org/', response['Location'] end it 'uses 302 for post requests if request is HTTP 1.0' do mock_app { post('/') { redirect '/'} } post('/', {}, 'HTTP_VERSION' => 'HTTP/1.0') assert_equal 302, status assert_equal '', body assert_equal 'http://example.org/', response['Location'] end it 'works behind a reverse proxy' do mock_app { get('/') { redirect '/foo' } } request = Rack::MockRequest.new(@app) response = request.get('/', 'HTTP_X_FORWARDED_HOST' => 'example.com', 'SERVER_PORT' => '8080') assert_equal 'http://example.com/foo', response['Location'] end it 'accepts absolute URIs' do mock_app do get('/') do redirect 'http://google.com' fail 'redirect should halt' end end get '/' assert_equal 302, status assert_equal '', body assert_equal 'http://google.com', response['Location'] end it 'accepts absolute URIs with a different schema' do mock_app do get('/') do redirect 'mailto:jsmith@example.com' fail 'redirect should halt' end end get '/' assert_equal 302, status assert_equal '', body assert_equal 'mailto:jsmith@example.com', response['Location'] end it 'accepts a URI object instead of a String' do mock_app do get('/') { redirect URI.parse('http://sinatrarb.com') } end get '/' assert_equal 302, status assert_equal '', body assert_equal 'http://sinatrarb.com', response['Location'] end end describe 'error' do it 'sets a status code and halts' do mock_app do get('/') do error 501 fail 'error should halt' end end get '/' assert_equal 501, status assert_equal '', body end it 'takes an optional body' do mock_app do get('/') do error 501, 'FAIL' fail 'error should halt' end end get '/' assert_equal 501, status assert_equal 'FAIL', body end it 'should not invoke error handler when setting status inside an error handler' do mock_app do disable :raise_errors not_found do body "not_found handler" status 404 end error do body "error handler" status 404 end get '/' do raise end end get '/' assert_equal 404, status assert_equal 'error handler', body end it 'should not reset the content-type to html for error handlers' do mock_app do disable :raise_errors before { content_type "application/json" } not_found { JSON.dump("error" => "Not Found") } end get '/' assert_equal 404, status assert_equal 'application/json', response.content_type end it 'should not invoke error handler when halting with 500 inside an error handler' do mock_app do disable :raise_errors not_found do body "not_found handler" halt 404 end error do body "error handler" halt 404 end get '/' do raise end end get '/' assert_equal 404, status assert_equal 'error handler', body end it 'should not invoke not_found handler when halting with 404 inside a not found handler' do mock_app do disable :raise_errors not_found do body "not_found handler" halt 500 end error do body "error handler" halt 500 end end get '/' assert_equal 500, status assert_equal 'not_found handler', body end it 'uses a 500 status code when first argument is a body' do mock_app do get('/') do error 'FAIL' fail 'error should halt' end end get '/' assert_equal 500, status assert_equal 'FAIL', body end end describe 'not_found' do it 'halts with a 404 status' do mock_app do get('/') do not_found fail 'not_found should halt' end end get '/' assert_equal 404, status assert_equal '', body end it 'does not set a X-Cascade header' do mock_app do get('/') do not_found fail 'not_found should halt' end end get '/' assert_equal 404, status assert_nil response.headers['X-Cascade'] end end describe 'headers' do it 'sets headers on the response object when given a Hash' do mock_app do get('/') do headers 'X-Foo' => 'bar', 'X-Baz' => 'bling' 'kthx' end end get '/' assert ok? assert_equal 'bar', response['X-Foo'] assert_equal 'bling', response['X-Baz'] assert_equal 'kthx', body end it 'returns the response headers hash when no hash provided' do mock_app do get('/') do headers['X-Foo'] = 'bar' 'kthx' end end get '/' assert ok? assert_equal 'bar', response['X-Foo'] end end describe 'session' do it 'uses the existing rack.session' do mock_app do get('/') do session[:foo] end end get('/', {}, { 'rack.session' => { :foo => 'bar' } }) assert_equal 'bar', body end it 'creates a new session when none provided' do mock_app do enable :sessions get('/') do assert session[:foo].nil? session[:foo] = 'bar' redirect '/hi' end get('/hi') do "hi #{session[:foo]}" end end get '/' follow_redirect! assert_equal 'hi bar', body end it 'inserts session middleware' do mock_app do enable :sessions get('/') do assert env['rack.session'] assert env['rack.session.options'] 'ok' end end get '/' assert_body 'ok' end it 'sets a default session secret' do mock_app do enable :sessions get('/') do secret = env['rack.session.options'][:secret] assert secret assert_equal secret, settings.session_secret 'ok' end end get '/' assert_body 'ok' end it 'allows disabling session secret' do mock_app do enable :sessions disable :session_secret get('/') do assert !env['rack.session.options'].include?(:session_secret) 'ok' end end # Silence warnings since Rack::Session::Cookie complains about the non-present session secret silence_warnings do get '/' end assert_body 'ok' end it 'accepts an options hash' do mock_app do set :sessions, :foo => :bar get('/') do assert_equal env['rack.session.options'][:foo], :bar 'ok' end end get '/' assert_body 'ok' end end describe 'mime_type' do include Sinatra::Helpers it "looks up mime types in Rack's MIME registry" do Rack::Mime::MIME_TYPES['.foo'] = 'application/foo' assert_equal 'application/foo', mime_type('foo') assert_equal 'application/foo', mime_type('.foo') assert_equal 'application/foo', mime_type(:foo) end it 'returns nil when given nil' do assert mime_type(nil).nil? end it 'returns nil when media type not registered' do assert mime_type(:bizzle).nil? end it 'returns the argument when given a media type string' do assert_equal 'text/plain', mime_type('text/plain') end it 'turns AcceptEntry into String' do type = mime_type(Sinatra::Request::AcceptEntry.new('text/plain')) assert_equal String, type.class assert_equal 'text/plain', type end end test 'Base.mime_type registers mime type' do mock_app do mime_type :foo, 'application/foo' get('/') do "foo is #{mime_type(:foo)}" end end get '/' assert_equal 'foo is application/foo', body end describe 'content_type' do it 'sets the Content-Type header' do mock_app do get('/') do content_type 'text/plain' 'Hello World' end end get '/' assert_equal 'text/plain;charset=utf-8', response['Content-Type'] assert_equal 'Hello World', body end it 'takes media type parameters (like charset=)' do mock_app do get('/') do content_type 'text/html', :charset => 'latin1' "

Hello, World

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

Hello, World

", body end it "looks up symbols in Rack's mime types dictionary" do Rack::Mime::MIME_TYPES['.foo'] = 'application/foo' mock_app do get('/foo.xml') do content_type :foo "I AM FOO" end end get '/foo.xml' assert ok? assert_equal 'application/foo', response['Content-Type'] assert_equal 'I AM FOO', body end it 'fails when no mime type is registered for the argument provided' do mock_app do get('/foo.xml') do content_type :bizzle "I AM FOO" end end assert_raises(RuntimeError) { get '/foo.xml' } end it 'only sets default charset for specific mime types' do tests_ran = false mock_app do mime_type :foo, 'text/foo' mime_type :bar, 'application/bar' mime_type :baz, 'application/baz' add_charset << mime_type(:baz) get('/') do assert_equal content_type(:txt), 'text/plain;charset=utf-8' assert_equal content_type(:css), 'text/css;charset=utf-8' assert_equal content_type(:html), 'text/html;charset=utf-8' assert_equal content_type(:foo), 'text/foo;charset=utf-8' assert_equal content_type(:xml), 'application/xml;charset=utf-8' assert_equal content_type(:xhtml), 'application/xhtml+xml;charset=utf-8' assert_equal content_type(:js), 'application/javascript;charset=utf-8' assert_equal content_type(:json), 'application/json' assert_equal content_type(:bar), 'application/bar' assert_equal content_type(:png), 'image/png' assert_equal content_type(:baz), 'application/baz;charset=utf-8' tests_ran = true "done" end end get '/' assert tests_ran end it 'handles already present params' do mock_app do get('/') do content_type 'foo/bar;level=1', :charset => 'utf-8' 'ok' end end get '/' assert_equal 'foo/bar;level=1, charset=utf-8', response['Content-Type'] end it 'does not add charset if present' do mock_app do get('/') do content_type 'text/plain;charset=utf-16' 'ok' end end get '/' assert_equal 'text/plain;charset=utf-16', response['Content-Type'] end it 'properly encodes parameters with delimiter characters' do mock_app do before '/comma' do content_type 'image/png', :comment => 'Hello, world!' end before '/semicolon' do content_type 'image/png', :comment => 'semi;colon' end before '/quote' do content_type 'image/png', :comment => '"Whatever."' end get('*') { 'ok' } end get '/comma' assert_equal 'image/png;comment="Hello, world!"', response['Content-Type'] get '/semicolon' assert_equal 'image/png;comment="semi;colon"', response['Content-Type'] get '/quote' assert_equal 'image/png;comment="\"Whatever.\""', response['Content-Type'] end end describe 'attachment' do def attachment_app(filename=nil) mock_app do get('/attachment') do attachment filename response.write("") end end end it 'sets the Content-Type response header' do attachment_app('test.xml') get '/attachment' assert_equal 'application/xml;charset=utf-8', response['Content-Type'] assert_equal '', body end it 'sets the Content-Type response header without extname' do attachment_app('test') get '/attachment' assert_equal 'text/html;charset=utf-8', response['Content-Type'] assert_equal '', body end it 'sets the Content-Type response header with extname' do mock_app do get('/attachment') do content_type :atom attachment 'test.xml' response.write("") end end get '/attachment' assert_equal 'application/atom+xml', response['Content-Type'] assert_equal '', body end end describe 'send_file' do setup do @file = File.dirname(__FILE__) + '/file.txt' File.open(@file, 'wb') { |io| io.write('Hello World') } end def teardown File.unlink @file @file = nil end def send_file_app(opts={}) path = @file mock_app { get '/file.txt' do send_file path, opts end } end it "sends the contents of the file" do send_file_app get '/file.txt' assert ok? assert_equal 'Hello World', body end it 'sets the Content-Type response header if a mime-type can be located' do send_file_app get '/file.txt' assert_equal 'text/plain;charset=utf-8', response['Content-Type'] end it 'sets the Content-Type response header if type option is set to a file extension' do send_file_app :type => 'html' get '/file.txt' assert_equal 'text/html;charset=utf-8', response['Content-Type'] end it 'sets the Content-Type response header if type option is set to a mime type' do send_file_app :type => 'application/octet-stream' get '/file.txt' assert_equal 'application/octet-stream', response['Content-Type'] end it 'sets the Content-Length response header' do send_file_app get '/file.txt' assert_equal 'Hello World'.length.to_s, response['Content-Length'] end it 'sets the Last-Modified response header' do send_file_app get '/file.txt' assert_equal File.mtime(@file).httpdate, response['Last-Modified'] end it 'allows passing in a different Last-Modified response header with :last_modified' do time = Time.now send_file_app :last_modified => time get '/file.txt' assert_equal time.httpdate, response['Last-Modified'] end it "returns a 404 when not found" do mock_app { get('/') { send_file 'this-file-does-not-exist.txt' } } get '/' assert not_found? end it "does not set the Content-Disposition header by default" do send_file_app get '/file.txt' assert_nil response['Content-Disposition'] end it "sets the Content-Disposition header when :disposition set to 'attachment'" do send_file_app :disposition => 'attachment' get '/file.txt' assert_equal 'attachment; filename="file.txt"', response['Content-Disposition'] end it "does not set add a file name if filename is false" do send_file_app :disposition => 'inline', :filename => false get '/file.txt' assert_equal 'inline', response['Content-Disposition'] end it "sets the Content-Disposition header when :disposition set to 'inline'" do send_file_app :disposition => 'inline' get '/file.txt' assert_equal 'inline; filename="file.txt"', response['Content-Disposition'] end it "does not raise an error when :disposition set to a frozen string" do send_file_app :disposition => 'inline'.freeze get '/file.txt' assert_equal 'inline; filename="file.txt"', response['Content-Disposition'] end it "sets the Content-Disposition header when :filename provided" do send_file_app :filename => 'foo.txt' get '/file.txt' assert_equal 'attachment; filename="foo.txt"', response['Content-Disposition'] end it 'allows setting a custom status code' do send_file_app :status => 201 get '/file.txt' assert_status 201 end it "is able to send files with unknown mime type" do @file = File.dirname(__FILE__) + '/file.foobar' File.open(@file, 'wb') { |io| io.write('Hello World') } send_file_app get '/file.txt' assert_equal 'application/octet-stream', response['Content-Type'] end it "does not override Content-Type if already set and no explicit type is given" do path = @file mock_app do get('/') do content_type :png send_file path end end get '/' assert_equal 'image/png', response['Content-Type'] end it "does override Content-Type even if already set, if explicit type is given" do path = @file mock_app do get('/') do content_type :png send_file path, :type => :gif end end get '/' assert_equal 'image/gif', response['Content-Type'] end it 'can have :status option as a string' do path = @file mock_app do post '/' do send_file path, :status => '422' end end post '/' assert_equal response.status, 422 end end describe 'cache_control' do setup do mock_app do get('/foo') do cache_control :public, :no_cache, :max_age => 60.0 'Hello World' end get('/bar') do cache_control :public, :no_cache 'Hello World' end end end it 'sets the Cache-Control header' do get '/foo' assert_equal ['public', 'no-cache', 'max-age=60'], response['Cache-Control'].split(', ') end it 'last argument does not have to be a hash' do get '/bar' assert_equal ['public', 'no-cache'], response['Cache-Control'].split(', ') end end describe 'expires' do setup do mock_app do get('/foo') do expires 60, :public, :no_cache 'Hello World' end get('/bar') { expires Time.now } get('/baz') { expires Time.at(0) } get('/blah') do obj = Object.new def obj.method_missing(*a, &b) 60.send(*a, &b) end def obj.is_a?(thing) 60.is_a?(thing) end expires obj, :public, :no_cache 'Hello World' end get('/boom') { expires '9999' } end end it 'sets the Cache-Control header' do get '/foo' assert_equal ['public', 'no-cache', 'max-age=60'], response['Cache-Control'].split(', ') end it 'sets the Expires header' do get '/foo' refute_nil response['Expires'] end it 'allows passing Time.now objects' do get '/bar' refute_nil response['Expires'] end it 'allows passing Time.at objects' do get '/baz' assert_equal 'Thu, 01 Jan 1970 00:00:00 GMT', response['Expires'] end it 'accepts values pretending to be a Numeric (like ActiveSupport::Duration)' do get '/blah' assert_equal ['public', 'no-cache', 'max-age=60'], response['Cache-Control'].split(', ') end it 'fails when Time.parse raises an ArgumentError' do assert_raises(ArgumentError) { get '/boom' } end end describe 'last_modified' do it 'ignores nil' do mock_app { get('/') { last_modified nil; 200; } } get '/' assert ! response['Last-Modified'] end it 'does not change a status other than 200' do mock_app do get('/') do status 299 last_modified Time.at(0) 'ok' end end get('/', {}, 'HTTP_IF_MODIFIED_SINCE' => 'Sun, 26 Sep 2030 23:43:52 GMT') assert_status 299 assert_body 'ok' end [Time.now, DateTime.now, Date.today, Time.now.to_i, Struct.new(:to_time).new(Time.now) ].each do |last_modified_time| describe "with #{last_modified_time.class.name}" do setup do mock_app do get('/') do last_modified last_modified_time 'Boo!' end end wrapper = Object.new.extend Sinatra::Helpers @last_modified_time = wrapper.time_for last_modified_time end # fixes strange missing test error when running complete test suite. it("does not complain about missing tests") { } context "when there's no If-Modified-Since header" do it 'sets the Last-Modified header to a valid RFC 2616 date value' do get '/' assert_equal @last_modified_time.httpdate, response['Last-Modified'] end it 'conditional GET misses and returns a body' do get '/' assert_equal 200, status assert_equal 'Boo!', body end end context "when there's an invalid If-Modified-Since header" do it 'sets the Last-Modified header to a valid RFC 2616 date value' do get('/', {}, { 'HTTP_IF_MODIFIED_SINCE' => 'a really weird date' }) assert_equal @last_modified_time.httpdate, response['Last-Modified'] end it 'conditional GET misses and returns a body' do get('/', {}, { 'HTTP_IF_MODIFIED_SINCE' => 'a really weird date' }) assert_equal 200, status assert_equal 'Boo!', body end end context "when the resource has been modified since the If-Modified-Since header date" do it 'sets the Last-Modified header to a valid RFC 2616 date value' do get('/', {}, { 'HTTP_IF_MODIFIED_SINCE' => (@last_modified_time - 1).httpdate }) assert_equal @last_modified_time.httpdate, response['Last-Modified'] end it 'conditional GET misses and returns a body' do get('/', {}, { 'HTTP_IF_MODIFIED_SINCE' => (@last_modified_time - 1).httpdate }) assert_equal 200, status assert_equal 'Boo!', body end it 'does not rely on string comparison' do mock_app do get('/compare') do last_modified "Mon, 18 Oct 2010 20:57:11 GMT" "foo" end end get('/compare', {}, { 'HTTP_IF_MODIFIED_SINCE' => 'Sun, 26 Sep 2010 23:43:52 GMT' }) assert_equal 200, status assert_equal 'foo', body get('/compare', {}, { 'HTTP_IF_MODIFIED_SINCE' => 'Sun, 26 Sep 2030 23:43:52 GMT' }) assert_equal 304, status assert_equal '', body end end context "when the resource has been modified on the exact If-Modified-Since header date" do it 'sets the Last-Modified header to a valid RFC 2616 date value' do get('/', {}, { 'HTTP_IF_MODIFIED_SINCE' => @last_modified_time.httpdate }) assert_equal @last_modified_time.httpdate, response['Last-Modified'] end it 'conditional GET matches and halts' do get( '/', {}, { 'HTTP_IF_MODIFIED_SINCE' => @last_modified_time.httpdate }) assert_equal 304, status assert_equal '', body end end context "when the resource hasn't been modified since the If-Modified-Since header date" do it 'sets the Last-Modified header to a valid RFC 2616 date value' do get('/', {}, { 'HTTP_IF_MODIFIED_SINCE' => (@last_modified_time + 1).httpdate }) assert_equal @last_modified_time.httpdate, response['Last-Modified'] end it 'conditional GET matches and halts' do get('/', {}, { 'HTTP_IF_MODIFIED_SINCE' => (@last_modified_time + 1).httpdate }) assert_equal 304, status assert_equal '', body end end context "If-Unmodified-Since" do it 'results in 200 if resource has not been modified' do get('/', {}, { 'HTTP_IF_UNMODIFIED_SINCE' => 'Sun, 26 Sep 2030 23:43:52 GMT' }) assert_equal 200, status assert_equal 'Boo!', body end it 'results in 412 if resource has been modified' do get('/', {}, { 'HTTP_IF_UNMODIFIED_SINCE' => Time.at(0).httpdate }) assert_equal 412, status assert_equal '', body end end end end end describe 'etag' do context "safe requests" do it 'returns 200 for normal requests' do mock_app do get('/') do etag 'foo' 'ok' end end get '/' assert_status 200 assert_body 'ok' end context "If-None-Match" do it 'returns 304 when If-None-Match is *' do mock_app do get('/') do etag 'foo' 'ok' end end get('/', {}, 'HTTP_IF_NONE_MATCH' => '*') assert_status 304 assert_body '' end it 'returns 200 when If-None-Match is * for new resources' do mock_app do get('/') do etag 'foo', :new_resource => true 'ok' end end get('/', {}, 'HTTP_IF_NONE_MATCH' => '*') assert_status 200 assert_body 'ok' end it 'returns 304 when If-None-Match is * for existing resources' do mock_app do get('/') do etag 'foo', :new_resource => false 'ok' end end get('/', {}, 'HTTP_IF_NONE_MATCH' => '*') assert_status 304 assert_body '' end it 'returns 304 when If-None-Match is the etag' do mock_app do get('/') do etag 'foo' 'ok' end end get('/', {}, 'HTTP_IF_NONE_MATCH' => '"foo"') assert_status 304 assert_body '' end it 'returns 304 when If-None-Match includes the etag' do mock_app do get('/') do etag 'foo' 'ok' end end get('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar", "foo"') assert_status 304 assert_body '' end it 'returns 200 when If-None-Match does not include the etag' do mock_app do get('/') do etag 'foo' 'ok' end end get('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar"') assert_status 200 assert_body 'ok' end it 'ignores If-Modified-Since if If-None-Match does not match' do mock_app do get('/') do etag 'foo' last_modified Time.at(0) 'ok' end end get('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar"') assert_status 200 assert_body 'ok' end it 'does not change a status code other than 2xx or 304' do mock_app do get('/') do status 499 etag 'foo' 'ok' end end get('/', {}, 'HTTP_IF_NONE_MATCH' => '"foo"') assert_status 499 assert_body 'ok' end it 'does change 2xx status codes' do mock_app do get('/') do status 299 etag 'foo' 'ok' end end get('/', {}, 'HTTP_IF_NONE_MATCH' => '"foo"') assert_status 304 assert_body '' end it 'does not send a body on 304 status codes' do mock_app do get('/') do status 304 etag 'foo' 'ok' end end get('/', {}, 'HTTP_IF_NONE_MATCH' => '"foo"') assert_status 304 assert_body '' end end context "If-Match" do it 'returns 200 when If-Match is the etag' do mock_app do get('/') do etag 'foo' 'ok' end end get('/', {}, 'HTTP_IF_MATCH' => '"foo"') assert_status 200 assert_body 'ok' end it 'returns 200 when If-Match includes the etag' do mock_app do get('/') do etag 'foo' 'ok' end end get('/', {}, 'HTTP_IF_MATCH' => '"foo", "bar"') assert_status 200 assert_body 'ok' end it 'returns 200 when If-Match is *' do mock_app do get('/') do etag 'foo' 'ok' end end get('/', {}, 'HTTP_IF_MATCH' => '*') assert_status 200 assert_body 'ok' end it 'returns 412 when If-Match is * for new resources' do mock_app do get('/') do etag 'foo', :new_resource => true 'ok' end end get('/', {}, 'HTTP_IF_MATCH' => '*') assert_status 412 assert_body '' end it 'returns 200 when If-Match is * for existing resources' do mock_app do get('/') do etag 'foo', :new_resource => false 'ok' end end get('/', {}, 'HTTP_IF_MATCH' => '*') assert_status 200 assert_body 'ok' end it 'returns 412 when If-Match does not include the etag' do mock_app do get('/') do etag 'foo' 'ok' end end get('/', {}, 'HTTP_IF_MATCH' => '"bar"') assert_status 412 assert_body '' end end end context "idempotent requests" do it 'returns 200 for normal requests' do mock_app do put('/') do etag 'foo' 'ok' end end put '/' assert_status 200 assert_body 'ok' end context "If-None-Match" do it 'returns 412 when If-None-Match is *' do mock_app do put('/') do etag 'foo' 'ok' end end put('/', {}, 'HTTP_IF_NONE_MATCH' => '*') assert_status 412 assert_body '' end it 'returns 200 when If-None-Match is * for new resources' do mock_app do put('/') do etag 'foo', :new_resource => true 'ok' end end put('/', {}, 'HTTP_IF_NONE_MATCH' => '*') assert_status 200 assert_body 'ok' end it 'returns 412 when If-None-Match is * for existing resources' do mock_app do put('/') do etag 'foo', :new_resource => false 'ok' end end put('/', {}, 'HTTP_IF_NONE_MATCH' => '*') assert_status 412 assert_body '' end it 'returns 412 when If-None-Match is the etag' do mock_app do put '/' do etag 'foo' 'ok' end end put('/', {}, 'HTTP_IF_NONE_MATCH' => '"foo"') assert_status 412 assert_body '' end it 'returns 412 when If-None-Match includes the etag' do mock_app do put('/') do etag 'foo' 'ok' end end put('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar", "foo"') assert_status 412 assert_body '' end it 'returns 200 when If-None-Match does not include the etag' do mock_app do put('/') do etag 'foo' 'ok' end end put('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar"') assert_status 200 assert_body 'ok' end it 'ignores If-Modified-Since if If-None-Match does not match' do mock_app do put('/') do etag 'foo' last_modified Time.at(0) 'ok' end end put('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar"') assert_status 200 assert_body 'ok' end end context "If-Match" do it 'returns 200 when If-Match is the etag' do mock_app do put('/') do etag 'foo' 'ok' end end put('/', {}, 'HTTP_IF_MATCH' => '"foo"') assert_status 200 assert_body 'ok' end it 'returns 200 when If-Match includes the etag' do mock_app do put('/') do etag 'foo' 'ok' end end put('/', {}, 'HTTP_IF_MATCH' => '"foo", "bar"') assert_status 200 assert_body 'ok' end it 'returns 200 when If-Match is *' do mock_app do put('/') do etag 'foo' 'ok' end end put('/', {}, 'HTTP_IF_MATCH' => '*') assert_status 200 assert_body 'ok' end it 'returns 412 when If-Match is * for new resources' do mock_app do put('/') do etag 'foo', :new_resource => true 'ok' end end put('/', {}, 'HTTP_IF_MATCH' => '*') assert_status 412 assert_body '' end it 'returns 200 when If-Match is * for existing resources' do mock_app do put('/') do etag 'foo', :new_resource => false 'ok' end end put('/', {}, 'HTTP_IF_MATCH' => '*') assert_status 200 assert_body 'ok' end it 'returns 412 when If-Match does not include the etag' do mock_app do put('/') do etag 'foo' 'ok' end end put('/', {}, 'HTTP_IF_MATCH' => '"bar"') assert_status 412 assert_body '' end end end context "post requests" do it 'returns 200 for normal requests' do mock_app do post('/') do etag 'foo' 'ok' end end post('/') assert_status 200 assert_body 'ok' end context "If-None-Match" do it 'returns 200 when If-None-Match is *' do mock_app do post('/') do etag 'foo' 'ok' end end post('/', {}, 'HTTP_IF_NONE_MATCH' => '*') assert_status 200 assert_body 'ok' end it 'returns 200 when If-None-Match is * for new resources' do mock_app do post('/') do etag 'foo', :new_resource => true 'ok' end end post('/', {}, 'HTTP_IF_NONE_MATCH' => '*') assert_status 200 assert_body 'ok' end it 'returns 412 when If-None-Match is * for existing resources' do mock_app do post('/') do etag 'foo', :new_resource => false 'ok' end end post('/', {}, 'HTTP_IF_NONE_MATCH' => '*') assert_status 412 assert_body '' end it 'returns 412 when If-None-Match is the etag' do mock_app do post('/') do etag 'foo' 'ok' end end post('/', {}, 'HTTP_IF_NONE_MATCH' => '"foo"') assert_status 412 assert_body '' end it 'returns 412 when If-None-Match includes the etag' do mock_app do post('/') do etag 'foo' 'ok' end end post('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar", "foo"') assert_status 412 assert_body '' end it 'returns 200 when If-None-Match does not include the etag' do mock_app do post('/') do etag 'foo' 'ok' end end post('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar"') assert_status 200 assert_body 'ok' end it 'ignores If-Modified-Since if If-None-Match does not match' do mock_app do post('/') do etag 'foo' last_modified Time.at(0) 'ok' end end post('/', {}, 'HTTP_IF_NONE_MATCH' => '"bar"') assert_status 200 assert_body 'ok' end end context "If-Match" do it 'returns 200 when If-Match is the etag' do mock_app do post('/') do etag 'foo' 'ok' end end post('/', {}, 'HTTP_IF_MATCH' => '"foo"') assert_status 200 assert_body 'ok' end it 'returns 200 when If-Match includes the etag' do mock_app do post('/') do etag 'foo' 'ok' end end post('/', {}, 'HTTP_IF_MATCH' => '"foo", "bar"') assert_status 200 assert_body 'ok' end it 'returns 412 when If-Match is *' do mock_app do post('/') do etag 'foo' 'ok' end end post('/', {}, 'HTTP_IF_MATCH' => '*') assert_status 412 assert_body '' end it 'returns 412 when If-Match is * for new resources' do mock_app do post('/') do etag 'foo', :new_resource => true 'ok' end end post('/', {}, 'HTTP_IF_MATCH' => '*') assert_status 412 assert_body '' end it 'returns 200 when If-Match is * for existing resources' do mock_app do post('/') do etag 'foo', :new_resource => false 'ok' end end post('/', {}, 'HTTP_IF_MATCH' => '*') assert_status 200 assert_body 'ok' end it 'returns 412 when If-Match does not include the etag' do mock_app do post('/') do etag 'foo' 'ok' end end post('/', {}, 'HTTP_IF_MATCH' => '"bar"') assert_status 412 assert_body '' end end end it 'uses a weak etag with the :weak option' do mock_app do get('/') do etag 'FOO', :weak "that's weak, dude." end end get '/' assert_equal 'W/"FOO"', response['ETag'] end it 'raises an ArgumentError for an invalid strength' do mock_app do get('/') do etag 'FOO', :w00t "that's weak, dude." end end assert_raises(ArgumentError) { get('/') } end end describe 'back' do it "makes redirecting back pretty" do mock_app { get('/foo') { redirect back } } get('/foo', {}, 'HTTP_REFERER' => 'http://github.com') assert redirect? assert_equal "http://github.com", response.location end end describe 'uri' do it 'generates absolute urls' do mock_app { get('/') { uri }} get '/' assert_equal 'http://example.org/', body end it 'includes path_info' do mock_app { get('/:name') { uri }} get '/foo' assert_equal 'http://example.org/foo', body end it 'allows passing an alternative to path_info' do mock_app { get('/:name') { uri '/bar' }} get '/foo' assert_equal 'http://example.org/bar', body end it 'includes script_name' do mock_app { get('/:name') { uri '/bar' }} get '/foo', {}, { "SCRIPT_NAME" => '/foo' } assert_equal 'http://example.org/foo/bar', body end it 'handles absolute URIs' do mock_app { get('/') { uri 'http://google.com' }} get '/' assert_equal 'http://google.com', body end it 'handles different protocols' do mock_app { get('/') { uri 'mailto:jsmith@example.com' }} get '/' assert_equal 'mailto:jsmith@example.com', body end it 'is aliased to #url' do mock_app { get('/') { url }} get '/' assert_equal 'http://example.org/', body end it 'is aliased to #to' do mock_app { get('/') { to }} get '/' assert_equal 'http://example.org/', body end it 'is case-insensitive' do mock_app { get('/:foo') { uri params[:foo] }} assert_equal get('HtTP://google.com').body, get('http://google.com').body end it 'generates relative link for invalid path' do mock_app { get('/') { uri 'htt^p://google.com' }} get '/' assert_equal 'http://example.org/htt^p://google.com', body end end describe 'logger' do it 'logging works when logging is enabled' do mock_app do enable :logging get('/') do logger.info "Program started" logger.warn "Nothing to do!" end end io = StringIO.new get '/', {}, 'rack.errors' => io assert io.string.include?("INFO -- : Program started") assert io.string.include?("WARN -- : Nothing to do") end it 'logging works when logging is disable, but no output is produced' do mock_app do disable :logging get('/') do logger.info "Program started" logger.warn "Nothing to do!" end end io = StringIO.new get '/', {}, 'rack.errors' => io assert !io.string.include?("INFO -- : Program started") assert !io.string.include?("WARN -- : Nothing to do") end it 'does not create a logger when logging is set to nil' do mock_app do set :logging, nil get('/') { logger.inspect } end get '/' assert_body 'nil' end end module ::HelperOne; def one; '1'; end; end module ::HelperTwo; def two; '2'; end; end describe 'Adding new helpers' do it 'takes a list of modules to mix into the app' do mock_app do helpers ::HelperOne, ::HelperTwo get('/one') { one } get('/two') { two } end get '/one' assert_equal '1', body get '/two' assert_equal '2', body end it 'takes a block to mix into the app' do mock_app do helpers do def foo 'foo' end end get('/') { foo } end get '/' assert_equal 'foo', body end it 'evaluates the block in class context so that methods can be aliased' do mock_app do helpers { alias_method :h, :escape_html } get('/') { h('42 < 43') } end get '/' assert ok? assert_equal '42 < 43', body end end end sinatra-2.0.8.1/test/indifferent_hash_test.rb000066400000000000000000000170441360317524000211730ustar00rootroot00000000000000# frozen_string_literal: true # # We don't need the full test helper for this standalone class. # require 'minitest/autorun' unless defined?(Minitest) # Suppress the ActiveSupport warning when this test is executed independently, # outside of the full suite, on older Rubies. ENV['SINATRA_ACTIVESUPPORT_WARNING'] = 'false' require_relative '../lib/sinatra/indifferent_hash' class TestIndifferentHashBasics < Minitest::Test def test_flattened_constructor hash = Sinatra::IndifferentHash[:a, 1, ?b, 2] assert_equal 1, hash[?a] assert_equal 2, hash[?b] end def test_pairs_constructor hash = Sinatra::IndifferentHash[[[:a, 1], [?b, 2]]] assert_equal 1, hash[?a] assert_equal 2, hash[?b] end def test_default_block hash = Sinatra::IndifferentHash.new { |h, k| h[k] = k.upcase } assert_nil hash.default assert_equal ?A, hash.default(:a) end def test_default_object hash = Sinatra::IndifferentHash.new(:a=>1, ?b=>2) assert_equal({ ?a=>1, ?b=>2 }, hash.default) assert_equal({ ?a=>1, ?b=>2 }, hash[:a]) end def test_default_assignment hash = Sinatra::IndifferentHash.new hash.default = { :a=>1, ?b=>2 } assert_equal({ ?a=>1, ?b=>2 }, hash.default) assert_equal({ ?a=>1, ?b=>2 }, hash[:a]) end def test_assignment hash = Sinatra::IndifferentHash.new hash[:a] = :a hash[?b] = :b hash[3] = 3 hash[:simple_nested] = { :a=>:a, ?b=>:b } assert_equal :a, hash[?a] assert_equal :b, hash[?b] assert_equal 3, hash[3] assert_equal({ ?a=>:a, ?b=>:b }, hash['simple_nested']) assert_nil hash[?d] end def test_merge! # merge! is already mostly tested by the different constructors, so we # really just need to test the block form here hash = Sinatra::IndifferentHash[:a=>'a', ?b=>'b', 3=>3] hash.merge!(?a=>'A', :b=>'B', :d=>'D') do |key, oldval, newval| "#{oldval}*#{key}*#{newval}" end assert_equal({ ?a=>'a*a*A', ?b=>'b*b*B', 3=>3, ?d=>'D' }, hash) end end class TestIndifferentHash < Minitest::Test def skip_if_lacking(meth) skip "Hash##{meth} not supported on this Ruby" unless Hash.method_defined?(meth) end def setup @hash = Sinatra::IndifferentHash[:a=>:a, ?b=>:b, 3=>3, :simple_nested=>{ :a=>:a, ?b=>:b }, :nested=>{ :a=>[{ :a=>:a, ?b=>:b }, :c, 4], ?f=>:f, 7=>7 } ] end def test_hash_constructor assert_equal :a, @hash[?a] assert_equal :b, @hash[?b] assert_equal 3, @hash[3] assert_equal({ ?a=>:a, ?b=>:b }, @hash['nested'][?a][0]) assert_equal :c, @hash['nested'][?a][1] assert_equal 4, @hash['nested'][?a][2] assert_equal :f, @hash['nested'][?f] assert_equal 7, @hash['nested'][7] assert_equal :a, @hash['simple_nested'][?a] assert_equal :b, @hash['simple_nested'][?b] assert_nil @hash[?d] end def test_assoc assert_nil @hash.assoc(:d) assert_equal [?a, :a], @hash.assoc(:a) assert_equal [?b, :b], @hash.assoc(:b) end def test_rassoc assert_nil @hash.rassoc(:d) assert_equal [?a, :a], @hash.rassoc(:a) assert_equal [?b, :b], @hash.rassoc(:b) assert_equal ['simple_nested', { ?a=>:a, ?b=>:b }], @hash.rassoc(:a=>:a, ?b=>:b) end def test_fetch assert_raises(KeyError) { @hash.fetch(:d) } assert_equal 1, @hash.fetch(:d, 1) assert_equal 2, @hash.fetch(:d) { 2 } assert_equal ?d, @hash.fetch(:d) { |k| k } assert_equal :a, @hash.fetch(:a, 1) assert_equal :a, @hash.fetch(:a) { 2 } end def test_symbolic_retrieval assert_equal :a, @hash[:a] assert_equal :b, @hash[:b] assert_equal({ ?a=>:a, ?b=>:b }, @hash[:nested][:a][0]) assert_equal :c, @hash[:nested][:a][1] assert_equal 4, @hash[:nested][:a][2] assert_equal :f, @hash[:nested][:f] assert_equal 7, @hash[:nested][7] assert_equal :a, @hash[:simple_nested][:a] assert_equal :b, @hash[:simple_nested][:b] assert_nil @hash[:d] end def test_key assert_nil @hash.key(:d) assert_equal ?a, @hash.key(:a) assert_equal 'simple_nested', @hash.key(:a=>:a, ?b=>:b) end def test_key? assert_operator @hash, :key?, :a assert_operator @hash, :key?, ?b assert_operator @hash, :key?, 3 refute_operator @hash, :key?, :d end def test_value? assert_operator @hash, :value?, :a assert_operator @hash, :value?, :b assert_operator @hash, :value?, 3 assert_operator @hash, :value?, { :a=>:a, ?b=>:b } refute_operator @hash, :value?, :d end def test_delete @hash.delete(:a) @hash.delete(?b) assert_nil @hash[:a] assert_nil @hash[?b] end def test_dig skip_if_lacking :dig assert_equal :a, @hash.dig(:a) assert_equal :b, @hash.dig(?b) assert_nil @hash.dig(:d) assert_equal :a, @hash.dig(:simple_nested, :a) assert_equal :b, @hash.dig('simple_nested', ?b) assert_nil @hash.dig('simple_nested', :d) assert_equal :a, @hash.dig(:nested, :a, 0, :a) assert_equal :b, @hash.dig('nested', ?a, 0, ?b) assert_nil @hash.dig('nested', ?a, 0, :d) end def test_slice skip_if_lacking :slice assert_equal Sinatra::IndifferentHash[a: :a], @hash.slice(:a) assert_equal Sinatra::IndifferentHash[b: :b], @hash.slice(?b) assert_equal Sinatra::IndifferentHash[3 => 3], @hash.slice(3) assert_equal Sinatra::IndifferentHash.new, @hash.slice(:d) assert_equal Sinatra::IndifferentHash[a: :a, b: :b, 3 => 3], @hash.slice(:a, :b, 3) assert_equal Sinatra::IndifferentHash[simple_nested: { a: :a, ?b => :b }], @hash.slice(:simple_nested) assert_equal Sinatra::IndifferentHash[nested: { a: [{ a: :a, ?b => :b }, :c, 4], ?f => :f, 7 => 7 }], @hash.slice(:nested) end def test_fetch_values skip_if_lacking :fetch_values assert_raises(KeyError) { @hash.fetch_values(3, :d) } assert_equal [:a, :b, 3, ?D], @hash.fetch_values(:a, ?b, 3, :d) { |k| k.upcase } end def test_values_at assert_equal [:a, :b, 3, nil], @hash.values_at(:a, ?b, 3, :d) end def test_merge # merge just calls merge!, which is already thoroughly tested hash2 = @hash.merge(?a=>1, :q=>2) { |key, oldval, newval| "#{oldval}*#{key}*#{newval}" } refute_equal @hash, hash2 assert_equal 'a*a*1', hash2[:a] assert_equal 2, hash2[?q] end def test_merge_with_multiple_argument hash = Sinatra::IndifferentHash.new.merge({a: 1}, {b: 2}, {c: 3}) assert_equal 1, hash[?a] assert_equal 2, hash[?b] assert_equal 3, hash[?c] hash2 = Sinatra::IndifferentHash[d: 4] hash3 = {e: 5} hash.merge!(hash2, hash3) assert_equal 4, hash[?d] assert_equal 5, hash[?e] end def test_replace @hash.replace(?a=>1, :q=>2) assert_equal({ ?a=>1, ?q=>2 }, @hash) end def test_transform_values! skip_if_lacking :transform_values! @hash.transform_values! { |v| v.is_a?(Hash) ? Hash[v.to_a] : v } assert_instance_of Sinatra::IndifferentHash, @hash[:simple_nested] end def test_transform_values skip_if_lacking :transform_values hash2 = @hash.transform_values { |v| v.respond_to?(:upcase) ? v.upcase : v } refute_equal @hash, hash2 assert_equal :A, hash2[:a] assert_equal :A, hash2[?a] end def test_transform_keys! skip_if_lacking :transform_keys! @hash.transform_keys! { |k| k.respond_to?(:to_sym) ? k.to_sym : k } assert_equal :a, @hash[:a] assert_equal :a, @hash[?a] end def test_transform_keys skip_if_lacking :transform_keys hash2 = @hash.transform_keys { |k| k.respond_to?(:upcase) ? k.upcase : k } refute_equal @hash, hash2 refute_operator hash2, :key?, :a refute_operator hash2, :key?, ?a assert_equal :a, hash2[:A] assert_equal :a, hash2[?A] end end sinatra-2.0.8.1/test/integration/000077500000000000000000000000001360317524000166245ustar00rootroot00000000000000sinatra-2.0.8.1/test/integration/app.rb000066400000000000000000000023421360317524000177320ustar00rootroot00000000000000$stderr.puts "loading" require 'sinatra' configure do set :foo, :bar end get '/app_file' do content_type :txt settings.app_file end get '/ping' do 'pong' end get '/stream' do stream do |out| sleep 0.1 out << "a" sleep 1.2 out << "b" end end get '/mainonly' do object = Object.new begin object.send(:get, '/foo') { } 'false' rescue NameError 'true' end end set :out, nil get '/async' do stream(:keep_open) { |o| (settings.out = o) << "hi!" } end get '/send' do settings.out << params[:msg] if params[:msg] settings.out.close if params[:close] "ok" end get '/send_file' do file = File.expand_path '../../views/a/in_a.str', __FILE__ send_file file end get '/streaming' do headers['Content-Length'] = '46' stream do |out| out << "It's gonna be legen -\n" sleep 0.5 out << " (wait for it) \n" puts headers sleep 1 out << "- dary!\n" end end class Subclass < Sinatra::Base set :out, nil get '/subclass/async' do stream(:keep_open) { |o| (settings.out = o) << "hi!" } end get '/subclass/send' do settings.out << params[:msg] if params[:msg] settings.out.close if params[:close] "ok" end end use Subclass $stderr.puts "starting" sinatra-2.0.8.1/test/integration_helper.rb000066400000000000000000000107321360317524000205130ustar00rootroot00000000000000require 'sinatra/base' require 'rbconfig' require 'open-uri' require 'sinatra/runner' module IntegrationHelper class BaseServer < Sinatra::Runner extend Enumerable attr_accessor :server, :port alias name server def self.all @all ||= [] end def self.each(&block) all.each(&block) end def self.run(server, port) new(server, port).run end def app_file File.expand_path('../integration/app.rb', __FILE__) end def environment "development" end def initialize(server, port) @installed, @pipe, @server, @port = nil, nil, server, port Server.all << self end def run return unless installed? kill @log = "" super at_exit { kill } end def installed? return @installed unless @installed.nil? s = server == 'HTTP' ? 'net/http/server' : server require s @installed = true rescue LoadError warn "#{server} is not installed, skipping integration tests" @installed = false end def command @command ||= begin cmd = ["APP_ENV=#{environment}", "exec"] if RbConfig.respond_to? :ruby cmd << RbConfig.ruby.inspect else file, dir = RbConfig::CONFIG.values_at('ruby_install_name', 'bindir') cmd << File.expand_path(file, dir).inspect end cmd << "-w" unless thin? || net_http_server? cmd << "-I" << File.expand_path('../../lib', __FILE__).inspect cmd << app_file.inspect << '-s' << server << '-o' << '127.0.0.1' << '-p' << port cmd << "-e" << environment.to_s << '2>&1' cmd.join " " end end def webrick? name.to_s == "webrick" end def thin? name.to_s == "thin" end def puma? name.to_s == "puma" end def reel? name.to_s == "reel" end def trinidad? name.to_s == "trinidad" end def net_http_server? name.to_s == 'HTTP' end def warnings log.scan(%r[(?:\(eval|lib/sinatra).*warning:.*$]) end def run_test(target, &block) retries ||= 3 target.server = self run unless alive? target.instance_eval(&block) rescue Exception => error retries -= 1 kill retries < 0 ? retry : raise(error) end end if RUBY_ENGINE == "jruby" class JRubyServer < BaseServer def start_vm require 'java' # Create a new container, set load paths and env # SINGLETHREAD means create a new runtime vm = org.jruby.embed.ScriptingContainer.new(org.jruby.embed.LocalContextScope::SINGLETHREAD) vm.load_paths = [File.expand_path('../../lib', __FILE__)] vm.environment = ENV.merge('APP_ENV' => environment.to_s) # This ensures processing of RUBYOPT which activates Bundler vm.provider.ruby_instance_config.process_arguments [] vm.argv = ['-s', server.to_s, '-o', '127.0.0.1', '-p', port.to_s, '-e', environment.to_s] # Set stdout/stderr so we can retrieve log @pipe = java.io.ByteArrayOutputStream.new vm.output = java.io.PrintStream.new(@pipe) vm.error = java.io.PrintStream.new(@pipe) Thread.new do # Hack to ensure that Kernel#caller has the same info as # when run from command-line, for Sinatra::Application.app_file. # Also, line numbers are zero-based in JRuby's parser vm.provider.runtime.current_context.set_file_and_line(app_file, 0) # Run the app vm.run_scriptlet org.jruby.embed.PathType::ABSOLUTE, app_file # terminate launches at_exit hooks which start server vm.terminate end end def run return unless installed? kill @thread = start_vm @started = Time.now warn "#{server} up and running on port #{port}" if ping at_exit { kill } end def log String.from_java_bytes @pipe.to_byte_array end def kill @thread.kill if @thread @thread = nil end end Server = JRubyServer else Server = BaseServer end def it(message, &block) Server.each do |server| next unless server.installed? super("with #{server.name}: #{message}") { server.run_test(self, &block) } end end def self.extend_object(obj) super base_port = 5000 + Process.pid % 100 Sinatra::Base.server.each_with_index do |server, index| Server.run(server, base_port+index) end end end sinatra-2.0.8.1/test/integration_test.rb000066400000000000000000000056231360317524000202160ustar00rootroot00000000000000require File.expand_path('../helper', __FILE__) require File.expand_path('../integration_helper', __FILE__) # These tests start a real server and talk to it over TCP. # Every test runs with every detected server. # # See test/integration/app.rb for the code of the app we test against. class IntegrationTest < Minitest::Test extend IntegrationHelper attr_accessor :server it('sets the app_file') { assert_equal server.app_file, server.get("/app_file") } it('only extends main') { assert_equal "true", server.get("/mainonly") } it 'logs once in development mode' do next if server.puma? or RUBY_ENGINE == 'jruby' random = "%064x" % Kernel.rand(2**256-1) server.get "/ping?x=#{random}" count = server.log.scan("GET /ping?x=#{random}").count if server.net_http_server? || server.reel? assert_equal 0, count elsif server.webrick? assert(count > 0) else assert_equal(1, count) end end it 'streams' do next if server.webrick? or server.trinidad? times, chunks = [Time.now], [] server.get_stream do |chunk| next if chunk.empty? chunks << chunk times << Time.now end assert_equal ["a", "b"], chunks assert times[1] - times[0] < 1 assert times[2] - times[1] > 1 end it 'streams async' do next unless server.thin? Timeout.timeout(3) do chunks = [] server.get_stream '/async' do |chunk| next if chunk.empty? chunks << chunk case chunk when "hi!" then server.get "/send?msg=hello" when "hello" then server.get "/send?close=1" end end assert_equal ['hi!', 'hello'], chunks end end it 'streams async from subclass' do next unless server.thin? Timeout.timeout(3) do chunks = [] server.get_stream '/subclass/async' do |chunk| next if chunk.empty? chunks << chunk case chunk when "hi!" then server.get "/subclass/send?msg=hello" when "hello" then server.get "/subclass/send?close=1" end end assert_equal ['hi!', 'hello'], chunks end end it 'starts the correct server' do exp = %r{ ==\sSinatra\s\(v#{Sinatra::VERSION}\)\s has\staken\sthe\sstage\son\s\d+\sfor\sdevelopment\s with\sbackup\sfrom\s#{server} }ix # because Net HTTP Server logs to $stderr by default assert_match exp, server.log unless server.net_http_server? || server.reel? end it 'does not generate warnings' do assert_raises(OpenURI::HTTPError) { server.get '/' } server.get '/app_file' assert_equal [], server.warnings end it 'sets the Content-Length response header when sending files' do response = server.get_response '/send_file' assert response['Content-Length'] end it "doesn't ignore Content-Length header when streaming" do response = server.get_response '/streaming' assert_equal '46', response['Content-Length'] end end sinatra-2.0.8.1/test/less_test.rb000066400000000000000000000034631360317524000166410ustar00rootroot00000000000000require File.expand_path('../helper', __FILE__) begin require 'less' class LessTest < Minitest::Test def less_app(options = {}, &block) mock_app do set :views, File.dirname(__FILE__) + '/views' set options get('/', &block) end get '/' end it 'renders inline Less strings' do less_app { less "@white_color: #fff; #main { background-color: @white_color }" } assert ok? assert_equal "#main{background-color:#ffffff;}", body.gsub(/\s/, "") end it 'defaults content type to css' do less_app { less "@white_color: #fff; #main { background-color: @white_color }" } assert ok? assert_equal "text/css;charset=utf-8", response['Content-Type'] end it 'defaults allows setting content type per route' do less_app do content_type :html less "@white_color: #fff; #main { background-color: @white_color }" end assert ok? assert_equal "text/html;charset=utf-8", response['Content-Type'] end it 'defaults allows setting content type globally' do less_app(:less => { :content_type => 'html' }) do less "@white_color: #fff; #main { background-color: @white_color }" end assert ok? assert_equal "text/html;charset=utf-8", response['Content-Type'] end it 'renders .less files in views path' do less_app { less :hello } assert ok? assert_equal "#main{background-color:#ffffff;}", body.gsub(/\s/, "") end it 'ignores the layout option' do less_app { less :hello, :layout => :layout2 } assert ok? assert_equal "#main{background-color:#ffffff;}", body.gsub(/\s/, "") end it "raises error if template not found" do mock_app { get('/') { less :no_such_template } } assert_raises(Errno::ENOENT) { get('/') } end end rescue LoadError warn "#{$!}: skipping less tests" end sinatra-2.0.8.1/test/liquid_test.rb000066400000000000000000000036421360317524000171610ustar00rootroot00000000000000require File.expand_path('../helper', __FILE__) begin require 'liquid' class LiquidTest < Minitest::Test def liquid_app(&block) mock_app do set :views, File.dirname(__FILE__) + '/views' get('/', &block) end get '/' end it 'renders inline liquid strings' do liquid_app { liquid '

Hiya

' } assert ok? assert_equal "

Hiya

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

Hello From Liquid

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

THIS. IS. {{ yield }}

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

THIS. IS. SPARTA

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

Liquid Layout!

\n

Hello World

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

Title

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

Subtitle

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

Contents.

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

Title

\n

Subtitle

\n

Contents.

\n" end end rescue LoadError warn "#{$!}: skipping liquid tests" end sinatra-2.0.8.1/test/mapped_error_test.rb000066400000000000000000000161601360317524000203500ustar00rootroot00000000000000require File.expand_path('../helper', __FILE__) class FooError < RuntimeError end class FooNotFound < Sinatra::NotFound end class FooSpecialError < RuntimeError def http_status; 501 end end class FooStatusOutOfRangeError < RuntimeError def code; 4000 end end class FooWithCode < RuntimeError def code; 419 end end class FirstError < RuntimeError; end class SecondError < RuntimeError; end class MappedErrorTest < Minitest::Test def test_default assert true end describe 'Exception Mappings' do it 'invokes handlers registered with ::error when raised' do mock_app do set :raise_errors, false error(FooError) { 'Foo!' } get('/') { raise FooError } end get '/' assert_equal 500, status assert_equal 'Foo!', body end it 'passes the exception object to the error handler' do mock_app do set :raise_errors, false error(FooError) { |e| assert_equal(FooError, e.class) } get('/') { raise FooError } end get('/') end it 'uses the Exception handler if no matching handler found' do mock_app do set :raise_errors, false error(Exception) { 'Exception!' } get('/') { raise FooError } end get '/' assert_equal 500, status assert_equal 'Exception!', body end it 'walks down inheritance chain for errors' do mock_app do set :raise_errors, false error(RuntimeError) { 'Exception!' } get('/') { raise FooError } end get '/' assert_equal 500, status assert_equal 'Exception!', body end it 'favors subclass handler over superclass handler if available' do mock_app do set :raise_errors, false error(Exception) { 'Exception!' } error(FooError) { 'FooError!' } error(RuntimeError) { 'Exception!' } get('/') { raise FooError } end get '/' assert_equal 500, status assert_equal 'FooError!', body end it "sets env['sinatra.error'] to the rescued exception" do mock_app do set :raise_errors, false error(FooError) do assert env.include?('sinatra.error') assert env['sinatra.error'].kind_of?(FooError) 'looks good' end get('/') { raise FooError } end get '/' assert_equal 'looks good', body end it "raises errors from the app when raise_errors set and no handler defined" do mock_app do set :raise_errors, true get('/') { raise FooError } end assert_raises(FooError) { get '/' } end it "calls error handlers before raising errors even when raise_errors is set" do mock_app do set :raise_errors, true error(FooError) { "she's there." } get('/') { raise FooError } end get '/' assert_equal 500, status end it "never raises Sinatra::NotFound beyond the application" do mock_app(Sinatra::Application) do get('/') { raise Sinatra::NotFound } end get '/' assert_equal 404, status end it "cascades for subclasses of Sinatra::NotFound" do mock_app do set :raise_errors, true error(FooNotFound) { "foo! not found." } get('/') { raise FooNotFound } end get '/' assert_equal 404, status assert_equal 'foo! not found.', body end it 'has a not_found method for backwards compatibility' do mock_app { not_found { "Lost, are we?" } } get '/test' assert_equal 404, status assert_equal "Lost, are we?", body end it 'inherits error mappings from base class' do base = Class.new(Sinatra::Base) base.error(FooError) { 'base class' } mock_app(base) do set :raise_errors, false get('/') { raise FooError } end get '/' assert_equal 'base class', body end it 'overrides error mappings in base class' do base = Class.new(Sinatra::Base) base.error(FooError) { 'base class' } mock_app(base) do set :raise_errors, false error(FooError) { 'subclass' } get('/') { raise FooError } end get '/' assert_equal 'subclass', body end it 'honors Exception#http_status if present' do mock_app do set :raise_errors, false error(501) { 'Foo!' } get('/') { raise FooSpecialError } end get '/' assert_equal 501, status assert_equal 'Foo!', body end it 'does not use Exception#code by default' do mock_app do set :raise_errors, false get('/') { raise FooWithCode } end get '/' assert_equal 500, status end it 'uses Exception#code if use_code is enabled' do mock_app do set :raise_errors, false set :use_code, true get('/') { raise FooWithCode } end get '/' assert_equal 419, status end it 'does not rely on Exception#code for invalid codes' do mock_app do set :raise_errors, false set :use_code, true get('/') { raise FooStatusOutOfRangeError } end get '/' assert_equal 500, status end it "allows a stack of exception_handlers" do mock_app do set :raise_errors, false error(FirstError) { 'First!' } error(SecondError) { 'Second!' } get('/'){ raise SecondError } end get '/' assert_equal 500, status assert_equal 'Second!', body end it "allows an exception handler to pass control to the next exception handler" do mock_app do set :raise_errors, false error(500, FirstError) { 'First!' } error(500, SecondError) { pass } get('/') { raise 500 } end get '/' assert_equal 500, status assert_equal 'First!', body end it "allows an exception handler to handle the exception" do mock_app do set :raise_errors, false error(500, FirstError) { 'First!' } error(500, SecondError) { 'Second!' } get('/') { raise 500 } end get '/' assert_equal 500, status assert_equal 'Second!', body end end describe 'Custom Error Pages' do it 'allows numeric status code mappings to be registered with ::error' do mock_app do set :raise_errors, false error(500) { 'Foo!' } get('/') { [500, {}, 'Internal Foo Error'] } end get '/' assert_equal 500, status assert_equal 'Foo!', body end it 'allows ranges of status code mappings to be registered with :error' do mock_app do set :raise_errors, false error(500..550) { "Error: #{response.status}" } get('/') { [507, {}, 'A very special error'] } end get '/' assert_equal 507, status assert_equal 'Error: 507', body end it 'allows passing more than one range' do mock_app do set :raise_errors, false error(409..411, 503..509) { "Error: #{response.status}" } get('/') { [507, {}, 'A very special error'] } end get '/' assert_equal 507, status assert_equal 'Error: 507', body end end end sinatra-2.0.8.1/test/markaby_test.rb000066400000000000000000000037221360317524000173170ustar00rootroot00000000000000require File.expand_path('../helper', __FILE__) begin require 'markaby' class MarkabyTest < Minitest::Test def markaby_app(&block) mock_app do set :views, File.dirname(__FILE__) + '/views' get('/', &block) end get '/' end it 'renders inline markaby strings' do markaby_app { markaby 'h1 "Hiya"' } assert ok? assert_equal "

Hiya

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

Hello From Markaby

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

THIS. IS. SPARTA

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

Markaby Layout!

Hello World

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

Hiya

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

THIS. IS. SPARTA

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

Markaby Layout!

Hello World

", body end it "raises error if template not found" do mock_app { get('/') { markaby :no_such_template } } assert_raises(Errno::ENOENT) { get('/') } end it "allows passing locals" do markaby_app { markaby 'text value', :locals => { :value => 'foo' } } assert ok? assert_equal 'foo', body end end rescue LoadError warn "#{$!}: skipping markaby tests" end sinatra-2.0.8.1/test/markdown_test.rb000066400000000000000000000042531360317524000175130ustar00rootroot00000000000000require File.expand_path('../helper', __FILE__) MarkdownTest = proc do def markdown_app(&block) mock_app do set :views, File.dirname(__FILE__) + '/views' get('/', &block) end get '/' end def setup Tilt.prefer engine, 'markdown', 'mkd', 'md' super end it 'uses the correct engine' do assert_equal engine, Tilt[:md] assert_equal engine, Tilt[:mkd] assert_equal engine, Tilt[:markdown] end it 'renders inline markdown strings' do markdown_app { markdown '# Hiya' } assert ok? assert_like "

Hiya

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

Hello From Markdown

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

SPARTA

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

Hello World

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

hi

', body end end # Will generate RDiscountTest, KramdownTest, etc. markdown_templates = Tilt.lazy_map['md'].map { |klass, _require_path| klass } markdown_templates.each do |template_name| begin template = Object.const_get(template_name) klass = Class.new(Minitest::Test) { define_method(:engine) { template } } klass.class_eval(&MarkdownTest) name = template_name.split('::').last.sub(/Template$/, 'Test') Object.const_set name, klass rescue LoadError, NameError warn "#{$!}: skipping markdown tests with #{template_name}" end end sinatra-2.0.8.1/test/mediawiki_test.rb000066400000000000000000000033561360317524000176370ustar00rootroot00000000000000require File.expand_path('../helper', __FILE__) begin require 'wikicloth' class MediaWikiTest < Minitest::Test def mediawiki_app(&block) mock_app do set :views, File.dirname(__FILE__) + '/views' get('/', &block) end get '/' end it 'supports both .mw and .mediawiki extensions' do assert_equal Tilt[:mw], Tilt[:mediawiki] end it 'renders inline mediawiki strings' do mediawiki_app { mediawiki "''Hiya''" } assert ok? assert_include body, 'Hiya' end it 'renders .mediawiki files in views path' do mediawiki_app { mediawiki :hello } assert ok? assert_include body, "Hello from MediaWiki" end it 'raises error if template not found' do mock_app { get('/') { mediawiki :no_such_template } } assert_raises(Errno::ENOENT) { get('/') } end it 'renders with inline layouts' do mock_app do layout { 'THIS. IS. #{yield.upcase}!' } get('/') { mediawiki 'Sparta', :layout_engine => :str } end get '/' assert ok? assert_like 'THIS. IS.

SPARTA

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

Hello World

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

hi

', body end end rescue LoadError warn "#{$!}: skipping mediawiki tests" end sinatra-2.0.8.1/test/middleware_test.rb000066400000000000000000000045111360317524000200030ustar00rootroot00000000000000require File.expand_path('../helper', __FILE__) class MiddlewareTest < Minitest::Test setup do @app = mock_app(Sinatra::Application) do get('/*')do response.headers['X-Tests'] = env['test.ran']. map { |n| n.split('::').last }. join(', ') env['PATH_INFO'] end end end class MockMiddleware < Struct.new(:app) def call(env) (env['test.ran'] ||= []) << self.class.to_s app.call(env) end end class UpcaseMiddleware < MockMiddleware def call(env) env['PATH_INFO'] = env['PATH_INFO'].upcase super end end it "is added with Sinatra::Application.use" do @app.use UpcaseMiddleware get '/hello-world' assert ok? assert_equal '/HELLO-WORLD', body end class DowncaseMiddleware < MockMiddleware def call(env) env['PATH_INFO'] = env['PATH_INFO'].downcase super end end it "runs in the order defined" do @app.use UpcaseMiddleware @app.use DowncaseMiddleware get '/Foo' assert_equal "/foo", body assert_equal "UpcaseMiddleware, DowncaseMiddleware", response['X-Tests'] end it "resets the prebuilt pipeline when new middleware is added" do @app.use UpcaseMiddleware get '/Foo' assert_equal "/FOO", body @app.use DowncaseMiddleware get '/Foo' assert_equal '/foo', body assert_equal "UpcaseMiddleware, DowncaseMiddleware", response['X-Tests'] end it "works when app is used as middleware" do @app.use UpcaseMiddleware @app = @app.new get '/Foo' assert_equal "/FOO", body assert_equal "UpcaseMiddleware", response['X-Tests'] end class FreezeMiddleware < MockMiddleware def call(env) req = Rack::Request.new(env) req.update_param('bar', 'baz'.freeze) super end end it "works when middleware adds a frozen param" do @app.use FreezeMiddleware get '/Foo' end class SpecialConstsMiddleware < MockMiddleware def call(env) req = Rack::Request.new(env) req.update_param('s', :s) req.update_param('i', 1) req.update_param('c', 3.to_c) req.update_param('t', true) req.update_param('f', false) req.update_param('n', nil) super end end it "handles params when the params contains true/false values" do @app.use SpecialConstsMiddleware get '/' end end sinatra-2.0.8.1/test/nokogiri_test.rb000066400000000000000000000031751360317524000175140ustar00rootroot00000000000000require File.expand_path('../helper', __FILE__) begin require 'nokogiri' class NokogiriTest < Minitest::Test def nokogiri_app(&block) mock_app do set :views, File.dirname(__FILE__) + '/views' get('/', &block) end get '/' end it 'renders inline Nokogiri strings' do nokogiri_app { nokogiri 'xml' } assert ok? assert_body %(\n) end it 'renders inline blocks' do nokogiri_app do @name = "Frank & Mary" nokogiri { |xml| xml.couple @name } end assert ok? assert_body %(\nFrank & Mary\n) end it 'renders .nokogiri files in views path' do nokogiri_app do @name = "Blue" nokogiri :hello end assert ok? assert_body "\nYou're my boy, Blue!\n" end it "renders with inline layouts" do next if Tilt::VERSION <= "1.1" mock_app do layout { %(xml.layout { xml << yield }) } get('/') { nokogiri %(xml.em 'Hello World') } end get '/' assert ok? assert_body %(\n\n Hello World\n\n) end it "renders with file layouts" do next if Tilt::VERSION <= "1.1" nokogiri_app { nokogiri %(xml.em 'Hello World'), :layout => :layout2 } assert ok? assert_body %(\n\n Hello World\n\n) end it "raises error if template not found" do mock_app { get('/') { nokogiri :no_such_template } } assert_raises(Errno::ENOENT) { get('/') } end end rescue LoadError warn "#{$!}: skipping nokogiri tests" end sinatra-2.0.8.1/test/public/000077500000000000000000000000001360317524000155575ustar00rootroot00000000000000sinatra-2.0.8.1/test/public/favicon.ico000066400000000000000000000000001360317524000176660ustar00rootroot00000000000000sinatra-2.0.8.1/test/public/hello+world.txt000066400000000000000000000001011360317524000205360ustar00rootroot00000000000000This is a test intended for the + sign in urls for static servingsinatra-2.0.8.1/test/rabl_test.rb000066400000000000000000000037021360317524000166070ustar00rootroot00000000000000require File.expand_path('../helper', __FILE__) begin require 'rabl' require 'ostruct' require 'json' require 'active_support/core_ext/hash/conversions' class RablTest < Minitest::Test def rabl_app(&block) mock_app { set :views, File.dirname(__FILE__) + '/views' get '/', &block } get '/' end it 'renders inline rabl strings' do rabl_app do @foo = OpenStruct.new(:baz => 'w00t') rabl %q{ object @foo attributes :baz } end assert ok? assert_equal '{"openstruct":{"baz":"w00t"}}', body end it 'renders .rabl files in views path' do rabl_app do @foo = OpenStruct.new(:bar => 'baz') rabl :hello end assert ok? assert_equal '{"openstruct":{"bar":"baz"}}', body end it "renders with file layouts" do rabl_app { @foo = OpenStruct.new(:bar => 'baz') rabl :hello, :layout => :layout2 } assert ok? assert_equal '{"qux":{"openstruct":{"bar":"baz"}}}', body end it "raises error if template not found" do mock_app { get('/') { rabl :no_such_template } } assert_raises(Errno::ENOENT) { get('/') } end it "passes rabl options to the rabl engine" do mock_app do get('/') do @foo = OpenStruct.new(:bar => 'baz') rabl %q{ object @foo attributes :bar }, :format => 'xml' end end get '/' assert ok? assert_body 'baz' end it "passes default rabl options to the rabl engine" do mock_app do set :rabl, :format => 'xml' get('/') do @foo = OpenStruct.new(:bar => 'baz') rabl %q{ object @foo attributes :bar } end end get '/' assert ok? assert_body 'baz' end end rescue LoadError warn "#{$!}: skipping rabl tests" end sinatra-2.0.8.1/test/rack_test.rb000066400000000000000000000020411360317524000166020ustar00rootroot00000000000000require File.expand_path('../helper', __FILE__) require 'rack' class RackTest < Minitest::Test setup do @foo = Sinatra.new { get('/foo') { 'foo' }} @bar = Sinatra.new { get('/bar') { 'bar' }} end def build(*middleware) endpoint = middleware.pop @app = Rack::Builder.app do middleware.each { |m| use m } run endpoint end end def check(*middleware) build(*middleware) assert get('/foo').ok? assert_body 'foo' assert get('/bar').ok? assert_body 'bar' end it 'works as middleware in front of Rack::Lock, with lock enabled' do @foo.enable :lock check(@foo, Rack::Lock, @bar) end it 'works as middleware behind Rack::Lock, with lock enabled' do @foo.enable :lock check(Rack::Lock, @foo, @bar) end it 'works as middleware in front of Rack::Lock, with lock disabled' do @foo.disable :lock check(@foo, Rack::Lock, @bar) end it 'works as middleware behind Rack::Lock, with lock disabled' do @foo.disable :lock check(Rack::Lock, @foo, @bar) end end sinatra-2.0.8.1/test/radius_test.rb000066400000000000000000000025311360317524000171550ustar00rootroot00000000000000require File.expand_path('../helper', __FILE__) begin require 'radius' class RadiusTest < Minitest::Test def radius_app(&block) mock_app do set :views, File.dirname(__FILE__) + '/views' get('/', &block) end get '/' end it 'renders inline radius strings' do radius_app { radius '

Hiya

' } assert ok? assert_equal "

Hiya

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

Hello From Radius

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

THIS. IS.

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

THIS. IS. SPARTA

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

Radius Layout!

\n

Hello World

\n", body end it "raises error if template not found" do mock_app { get('/') { radius :no_such_template } } assert_raises(Errno::ENOENT) { get('/') } end it "allows passing locals" do radius_app { radius '', :locals => { :value => 'foo' } } assert ok? assert_equal 'foo', body end end rescue LoadError warn "#{$!}: skipping radius tests" end sinatra-2.0.8.1/test/rdoc_test.rb000066400000000000000000000032631360317524000166200ustar00rootroot00000000000000require File.expand_path('../helper', __FILE__) begin require 'rdoc' require 'rdoc/markup/to_html' class RdocTest < Minitest::Test def rdoc_app(&block) mock_app do set :views, File.dirname(__FILE__) + '/views' get('/', &block) end get '/' end it 'renders inline rdoc strings' do rdoc_app { rdoc '= Hiya' } assert ok? assert_body(/]*>Hiya(¶<\/a> ↑<\/a><\/span>)?<\/h1>/) end it 'renders .rdoc files in views path' do rdoc_app { rdoc :hello } assert ok? assert_body(/]*>Hello From RDoc(¶<\/a> ↑<\/a><\/span>)?<\/h1>/) end it "raises error if template not found" do mock_app { get('/') { rdoc :no_such_template } } assert_raises(Errno::ENOENT) { get('/') } end it "renders with inline layouts" do mock_app do layout { 'THIS. IS. #{yield.upcase}!' } get('/') { rdoc 'Sparta', :layout_engine => :str } end get '/' assert ok? assert_like 'THIS. IS.

SPARTA

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

Hello World

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

hi

', body end end rescue LoadError warn "#{$!}: skipping rdoc tests" end sinatra-2.0.8.1/test/readme_test.rb000066400000000000000000000052651360317524000171320ustar00rootroot00000000000000# Tests to check if all the README examples work. require File.expand_path('../helper', __FILE__) class ReadmeTest < Minitest::Test example do mock_app { get('/') { 'Hello world!' } } get '/' assert_body 'Hello world!' end section "Routes" do example do mock_app do get('/') { ".. show something .." } post('/') { ".. create something .." } put('/') { ".. replace something .." } patch('/') { ".. modify something .." } delete('/') { ".. annihilate something .." } options('/') { ".. appease something .." } link('/') { ".. affiliate something .." } unlink('/') { ".. separate something .." } end get '/' assert_body '.. show something ..' post '/' assert_body '.. create something ..' put '/' assert_body '.. replace something ..' patch '/' assert_body '.. modify something ..' delete '/' assert_body '.. annihilate something ..' options '/' assert_body '.. appease something ..' link '/' assert_body '.. affiliate something ..' unlink '/' assert_body '.. separate something ..' end example do mock_app do get('/hello/:name') do # matches "GET /hello/foo" and "GET /hello/bar" # params[:name] is 'foo' or 'bar' "Hello #{params[:name]}!" end end get '/hello/foo' assert_body 'Hello foo!' get '/hello/bar' assert_body 'Hello bar!' end example do mock_app { get('/hello/:name') { |n| "Hello #{n}!" } } get '/hello/foo' assert_body 'Hello foo!' get '/hello/bar' assert_body 'Hello bar!' end example do mock_app do get('/say/*/to/*') do # matches /say/hello/to/world params[:splat].inspect # => ["hello", "world"] end get('/download/*.*') do # matches /download/path/to/file.xml params[:splat].inspect # => ["path/to/file", "xml"] end end get "/say/hello/to/world" assert_body '["hello", "world"]' get "/download/path/to/file.xml" assert_body '["path/to/file", "xml"]' end example do mock_app do get(%r{/hello/([\w]+)}) { "Hello, #{params[:captures].first}!" } end get '/hello/foo' assert_body 'Hello, foo!' get '/hello/bar' assert_body 'Hello, bar!' end example do mock_app do get( %r{/hello/([\w]+)}) { |c| "Hello, #{c}!" } end get '/hello/foo' assert_body 'Hello, foo!' get '/hello/bar' assert_body 'Hello, bar!' end end end sinatra-2.0.8.1/test/request_test.rb000066400000000000000000000073601360317524000173630ustar00rootroot00000000000000require File.expand_path('../helper', __FILE__) require 'stringio' class RequestTest < Minitest::Test it 'responds to #user_agent' do request = Sinatra::Request.new({'HTTP_USER_AGENT' => 'Test'}) assert request.respond_to?(:user_agent) assert_equal 'Test', request.user_agent end it 'parses POST params when Content-Type is form-dataish' do request = Sinatra::Request.new( 'REQUEST_METHOD' => 'PUT', 'CONTENT_TYPE' => 'application/x-www-form-urlencoded', 'rack.input' => StringIO.new('foo=bar') ) assert_equal 'bar', request.params['foo'] end it 'is secure when the url scheme is https' do request = Sinatra::Request.new('rack.url_scheme' => 'https') assert request.secure? end it 'is not secure when the url scheme is http' do request = Sinatra::Request.new('rack.url_scheme' => 'http') assert !request.secure? end it 'respects X-Forwarded-Proto header for proxied SSL' do request = Sinatra::Request.new('HTTP_X_FORWARDED_PROTO' => 'https') assert request.secure? end it 'is possible to marshal params' do request = Sinatra::Request.new( 'REQUEST_METHOD' => 'PUT', 'CONTENT_TYPE' => 'application/x-www-form-urlencoded', 'rack.input' => StringIO.new('foo=bar') ) Sinatra::IndifferentHash[request.params] dumped = Marshal.dump(request.params) assert_equal 'bar', Marshal.load(dumped)['foo'] end it "exposes the preferred type's parameters" do request = Sinatra::Request.new( 'HTTP_ACCEPT' => 'image/jpeg; compress=0.25' ) assert_equal({ 'compress' => '0.25' }, request.preferred_type.params) end it "raises Sinatra::BadRequest when params contain conflicting types" do request = Sinatra::Request.new 'QUERY_STRING' => 'foo=&foo[]=' assert_raises(Sinatra::BadRequest) { request.params } end it "makes accept types behave like strings" do request = Sinatra::Request.new('HTTP_ACCEPT' => 'image/jpeg; compress=0.25') assert request.accept?('image/jpeg') assert_equal 'image/jpeg', request.preferred_type.to_s assert_equal 'image/jpeg; compress=0.25', request.preferred_type.to_s(true) assert_equal 'image/jpeg', request.preferred_type.to_str assert_equal 'image', request.preferred_type.split('/').first String.instance_methods.each do |method| next unless "".respond_to? method assert request.preferred_type.respond_to?(method), "responds to #{method}" end end it "accepts types when wildcards are requested" do request = Sinatra::Request.new('HTTP_ACCEPT' => 'image/*') assert request.accept?('image/jpeg') end it "properly decodes MIME type parameters" do request = Sinatra::Request.new( 'HTTP_ACCEPT' => 'image/jpeg;unquoted=0.25;quoted="0.25";chartest="\";,\x"' ) expected = { 'unquoted' => '0.25', 'quoted' => '0.25', 'chartest' => '";,x' } assert_equal(expected, request.preferred_type.params) end it 'accepts */* when HTTP_ACCEPT is not present in the request' do request = Sinatra::Request.new Hash.new assert_equal 1, request.accept.size assert request.accept?('text/html') assert_equal '*/*', request.preferred_type.to_s assert_equal '*/*', request.preferred_type.to_s(true) end it 'accepts */* when HTTP_ACCEPT is blank in the request' do request = Sinatra::Request.new 'HTTP_ACCEPT' => '' assert_equal 1, request.accept.size assert request.accept?('text/html') assert_equal '*/*', request.preferred_type.to_s assert_equal '*/*', request.preferred_type.to_s(true) end it 'will not accept types not specified in HTTP_ACCEPT when HTTP_ACCEPT is provided' do request = Sinatra::Request.new 'HTTP_ACCEPT' => 'application/json' assert !request.accept?('text/html') end end sinatra-2.0.8.1/test/response_test.rb000066400000000000000000000044261360317524000175310ustar00rootroot00000000000000# encoding: utf-8 require File.expand_path('../helper', __FILE__) class ResponseTest < Minitest::Test setup { @response = Sinatra::Response.new } def assert_same_body(a, b) assert_equal a.to_enum(:each).to_a, b.to_enum(:each).to_a end it "initializes with 200, text/html, and empty body" do assert_equal 200, @response.status assert_equal 'text/html', @response['Content-Type'] assert_equal [], @response.body end it 'uses case insensitive headers' do @response['content-type'] = 'application/foo' assert_equal 'application/foo', @response['Content-Type'] assert_equal 'application/foo', @response['CONTENT-TYPE'] end it 'writes to body' do @response.body = 'Hello' @response.write ' World' assert_equal 'Hello World', @response.body.join end [204, 304].each do |status_code| it "removes the Content-Type header and body when response status is #{status_code}" do @response.status = status_code @response.body = ['Hello World'] assert_equal [status_code, {}, []], @response.finish end end [200, 201, 202, 301, 302, 400, 401, 403, 404, 500].each do |status_code| it "will not removes the Content-Type header and body when response status is #{status_code}" do @response.status = status_code @response.body = ['Hello World'] assert_equal [ status_code, { 'Content-Type' => 'text/html', 'Content-Length' => '11' }, ['Hello World'] ], @response.finish end end it 'Calculates the Content-Length using the bytesize of the body' do @response.body = ['Hello', 'World!', '✈'] _, headers, body = @response.finish assert_equal '14', headers['Content-Length'] assert_same_body @response.body, body end it 'does not call #to_ary or #inject on the body' do object = Object.new def object.inject(*) fail 'called' end def object.to_ary(*) fail 'called' end def object.each(*) end @response.body = object assert @response.finish end it 'does not nest a Sinatra::Response' do @response.body = Sinatra::Response.new ["foo"] assert_same_body @response.body, ["foo"] end it 'does not nest a Rack::Response' do @response.body = Rack::Response.new ["foo"] assert_same_body @response.body, ["foo"] end end sinatra-2.0.8.1/test/result_test.rb000066400000000000000000000033431360317524000172060ustar00rootroot00000000000000require File.expand_path('../helper', __FILE__) class ResultTest < Minitest::Test it "sets response.body when result is a String" do mock_app { get('/') { 'Hello World' } } get '/' assert ok? assert_equal 'Hello World', body end it "sets response.body when result is an Array of Strings" do mock_app { get('/') { ['Hello', 'World'] } } get '/' assert ok? assert_equal 'HelloWorld', body end it "sets response.body when result responds to #each" do mock_app do get('/') do res = lambda { 'Hello World' } def res.each ; yield call ; end return res end end get '/' assert ok? assert_equal 'Hello World', body end it "sets response.body to [] when result is nil" do mock_app { get( '/') { nil } } get '/' assert ok? assert_equal '', body end it "sets status, headers, and body when result is a Rack response tuple" do mock_app { get('/') { [203, {'Content-Type' => 'foo/bar'}, 'Hello World'] } } get '/' assert_equal 203, status assert_equal 'foo/bar', response['Content-Type'] assert_equal 'Hello World', body end it "sets status and body when result is a two-tuple" do mock_app { get('/') { [409, 'formula of'] } } get '/' assert_equal 409, status assert_equal 'formula of', body end it "raises a ArgumentError when result is a non two or three tuple Array" do mock_app { get('/') { [409, 'formula of', 'something else', 'even more'] } } assert_raises(ArgumentError) { get '/' } end it "sets status when result is a Integer status code" do mock_app { get('/') { 205 } } get '/' assert_equal 205, status assert_equal '', body end end sinatra-2.0.8.1/test/route_added_hook_test.rb000066400000000000000000000025141360317524000211660ustar00rootroot00000000000000require File.expand_path('../helper', __FILE__) module RouteAddedTest @routes, @procs = [], [] def self.routes ; @routes ; end def self.procs ; @procs ; end def self.route_added(verb, path, proc) @routes << [verb, path] @procs << proc end end class RouteAddedHookTest < Minitest::Test setup do RouteAddedTest.routes.clear RouteAddedTest.procs.clear end it "should be notified of an added route" do mock_app(Class.new(Sinatra::Base)) do register RouteAddedTest get('/') {} end assert_equal [["GET", "/"], ["HEAD", "/"]], RouteAddedTest.routes end it "should include hooks from superclass" do a = Class.new(Class.new(Sinatra::Base)) b = Class.new(a) a.register RouteAddedTest b.class_eval { post("/sub_app_route") {} } assert_equal [["POST", "/sub_app_route"]], RouteAddedTest.routes end it "should only run once per extension" do mock_app(Class.new(Sinatra::Base)) do register RouteAddedTest register RouteAddedTest get('/') {} end assert_equal [["GET", "/"], ["HEAD", "/"]], RouteAddedTest.routes end it "should pass route blocks as an argument" do mock_app(Class.new(Sinatra::Base)) do register RouteAddedTest get('/') {} end assert_kind_of Proc, RouteAddedTest.procs.first end end sinatra-2.0.8.1/test/routing_test.rb000066400000000000000000001124761360317524000173670ustar00rootroot00000000000000# I like coding: UTF-8 require File.expand_path('../helper', __FILE__) # Helper method for easy route pattern matching testing def route_def(pattern) mock_app { get(pattern) { } } end class PatternLookAlike def to_pattern(*) self end def params(input) { "one" => "this", "two" => "is", "three" => "a", "four" => "test" } end end class RoutingTest < Minitest::Test %w[get put post delete options patch link unlink].each do |verb| it "defines #{verb.upcase} request handlers with #{verb}" do mock_app { send verb, '/hello' do 'Hello World' end } request = Rack::MockRequest.new(@app) response = request.request(verb.upcase, '/hello', {}) assert response.ok? assert_equal 'Hello World', response.body end end it "defines HEAD request handlers with HEAD" do mock_app { head '/hello' do response['X-Hello'] = 'World!' 'remove me' end } request = Rack::MockRequest.new(@app) response = request.request('HEAD', '/hello', {}) assert response.ok? assert_equal 'World!', response['X-Hello'] assert_equal '', response.body end it "400s when request params contain conflicting types" do mock_app { get('/foo') { } } request = Rack::MockRequest.new(@app) response = request.request('GET', '/foo?bar=&bar[]=', {}) assert response.bad_request? end it "404s when no route satisfies the request" do mock_app { get('/foo') { } } get '/bar' assert_equal 404, status end it "404s and sets X-Cascade header when no route satisfies the request" do mock_app { get('/foo') { } } get '/bar' assert_equal 404, status assert_equal 'pass', response.headers['X-Cascade'] end it "404s and does not set X-Cascade header when no route satisfies the request and x_cascade has been disabled" do mock_app { disable :x_cascade get('/foo') { } } get '/bar' assert_equal 404, status assert_nil response.headers['X-Cascade'] end it "allows using unicode" do mock_app do get('/föö') { } end get '/f%C3%B6%C3%B6' assert_equal 200, status end it "it handles encoded slashes correctly" do mock_app { set :protection, :except => :path_traversal get("/:a") { |a| a } } get '/foo%2Fbar' assert_equal 200, status assert_body "foo/bar" end it "it handles encoded colons correctly" do mock_app { get("/\\:") { 'a' } get("/a/\\:") { 'b' } get("/a/\\:/b") { 'c' } get("/a/b\\:") { 'd' } get("/a/b\\: ") { 'e' } } get '/:' assert_equal 200, status assert_body "a" get '/%3a' assert_equal 200, status assert_body "a" get '/a/:' assert_equal 200, status assert_body "b" get '/a/%3a' assert_equal 200, status assert_body "b" get '/a/:/b' assert_equal 200, status assert_body "c" get '/a/%3A/b' assert_equal 200, status assert_body "c" get '/a/b:' assert_equal 200, status assert_body "d" get '/a/b%3a' assert_equal 200, status assert_body "d" get '/a/b%3a%20' assert_equal 200, status assert_body "e" get '/a/b%3a+' assert_equal 200, status assert_body "e" end it "overrides the content-type in error handlers" do mock_app { before { content_type 'text/plain' } error Sinatra::NotFound do content_type "text/html" "

Not Found

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

Not Found

", response.body end it "recalculates body length correctly for 404 response" do mock_app { get '/' do @response["Content-Length"] = "30" raise Sinatra::NotFound end } get "/" assert_equal "18", response["Content-Length"] assert_equal 404, status end it "captures the exception message of a raised NotFound" do mock_app { get '/' do raise Sinatra::NotFound, "This is not a drill" end } get "/" assert_equal "19", response["Content-Length"] assert_equal 404, status assert_equal "This is not a drill", response.body end it "captures the exception message of a raised BadRequest" do mock_app { get '/' do raise Sinatra::BadRequest, "This is not a drill either" end } get "/" assert_equal "26", response["Content-Length"] assert_equal 400, status assert_equal "This is not a drill either", response.body end it "captures the custom exception message of a BadRequest" do mock_app { get('/') {} error Sinatra::BadRequest do 'This is not a drill either' end } get "/", "foo" => "", "foo[]" => "" assert_equal "26", response["Content-Length"] assert_equal 400, status assert_equal "This is not a drill either", response.body end it "returns empty when unmatched with any regex captures" do mock_app do before do # noop end get '/hello' do params.to_s end end assert get('/hello').ok? assert_body '{}' end it "uses 404 error handler for not matching route" do mock_app { not_found do "nf" end error 404 do "e" end } get "/" assert_equal "e", body assert_equal 404, status end it 'matches empty PATH_INFO to "/" if no route is defined for ""' do mock_app do get '/' do 'worked' end end get '/', {}, "PATH_INFO" => "" assert ok? assert_equal 'worked', body end it 'matches empty PATH_INFO to "" if a route is defined for ""' do mock_app do disable :protection get '/' do 'did not work' end get '' do 'worked' end end get '/', {}, "PATH_INFO" => "" assert ok? assert_equal 'worked', body end it 'takes multiple definitions of a route' do mock_app { user_agent(/Foo/) get '/foo' do 'foo' end get '/foo' do 'not foo' end } get '/foo', {}, 'HTTP_USER_AGENT' => 'Foo' assert ok? assert_equal 'foo', body get '/foo' assert ok? assert_equal 'not foo', body end it "exposes params with indifferent hash" do mock_app { get '/:foo' do assert_equal 'bar', params['foo'] assert params.has_key?('foo') assert_equal 'bar', params[:foo] assert params.has_key?(:foo) 'well, alright' end } get '/bar' assert_equal 'well, alright', body end it "handles params without a value" do mock_app { get '/' do assert_nil params.fetch('foo') "Given: #{params.keys.sort.join(',')}" end } get '/?foo' assert_equal 'Given: foo', body end it "merges named params and query string params in params" do mock_app { get '/:foo' do assert_equal 'bar', params['foo'] assert_equal 'biz', params['baz'] end } get '/bar?baz=biz' assert ok? end it "supports named params like /hello/:person" do mock_app { get '/hello/:person' do "Hello #{params['person']}" end } get '/hello/Frank' assert_equal 'Hello Frank', body end it "supports optional named params like /?:foo?/?:bar?" do mock_app { get '/?:foo?/?:bar?' do "foo=#{params[:foo]};bar=#{params[:bar]}" end } get '/hello/world' assert ok? assert_equal "foo=hello;bar=world", body get '/hello' assert ok? assert_equal "foo=hello;bar=", body get '/' assert ok? assert_equal "foo=;bar=", body end it "uses the default encoding for named params" do mock_app { set :default_encoding ,'ISO-8859-1' get '/:foo/:bar' do "foo=#{params[:foo].encoding};bar=#{params[:bar].encoding}" end } get '/f%C3%B6%C3%B6/b%C3%B6%C3%B6' assert ok? assert_equal 'foo=ISO-8859-1;bar=ISO-8859-1', body end it "supports named captures like %r{/hello/(?[^/?#]+)}" do mock_app { get Regexp.new('/hello/(?[^/?#]+)') do "Hello #{params['person']}" end } get '/hello/Frank' assert_equal 'Hello Frank', body end it "supports optional named captures like %r{/page(?.[^/?#]+)?}" do mock_app { get Regexp.new('/page(?.[^/?#]+)?') do "format=#{params[:format]}" end } get '/page.html' assert ok? assert_equal "format=.html", body get '/page.xml' assert ok? assert_equal "format=.xml", body get '/page' assert ok? assert_equal "format=", body end it 'uses the default encoding for named captures' do mock_app { set :default_encoding ,'ISO-8859-1' get Regexp.new('/page(?.[^/?#]+)?') do "format=#{params[:format].encoding};captures=#{params[:captures][0].encoding}" end } get '/page.f%C3%B6' assert ok? assert_equal 'format=ISO-8859-1;captures=ISO-8859-1', body end it 'does not concatenate params with the same name' do mock_app { get('/:foo') { params[:foo] } } get '/a?foo=b' assert_body 'a' end it "supports single splat params like /*" do mock_app { get '/*' do assert params['splat'].kind_of?(Array) params['splat'].join "\n" end } get '/foo' assert_equal "foo", body get '/foo/bar/baz' assert_equal "foo/bar/baz", body end it "supports mixing multiple splat params like /*/foo/*/*" do mock_app { get '/*/foo/*/*' do assert params['splat'].kind_of?(Array) params['splat'].join "\n" end } get '/bar/foo/bling/baz/boom' assert_equal "bar\nbling\nbaz/boom", body get '/bar/foo/baz' assert not_found? end it "supports mixing named and splat params like /:foo/*" do mock_app { get '/:foo/*' do assert_equal 'foo', params['foo'] assert_equal ['bar/baz'], params['splat'] end } get '/foo/bar/baz' assert ok? end it "matches a dot ('.') as part of a named param" do mock_app { get '/:foo/:bar' do params[:foo] end } get '/user@example.com/name' assert_equal 200, response.status assert_equal 'user@example.com', body end it "matches a literal dot ('.') outside of named params" do mock_app { get '/:file.:ext' do assert_equal 'pony', params[:file] assert_equal 'jpg', params[:ext] 'right on' end } get '/pony.jpg' assert_equal 200, response.status assert_equal 'right on', body end it "literally matches dot in paths" do route_def '/test.bar' get '/test.bar' assert ok? get 'test0bar' assert not_found? end it "literally matches dollar sign in paths" do route_def '/test$/' get '/test$/' assert ok? end it "literally matches plus sign in paths" do route_def '/te+st/' get '/te%2Bst/' assert ok? get '/teeeeeeest/' assert not_found? end it "does not convert plus sign into space as the value of a named param" do mock_app do get '/:test' do params["test"] end end get '/bob+ross' assert ok? assert_equal 'bob+ross', body end it "literally matches parens in paths when escaped" do route_def '/test\(bar\)/' get '/test(bar)/' assert ok? end it "supports basic nested params" do mock_app { get '/hi' do params["person"]["name"] end } get "/hi?person[name]=John+Doe" assert ok? assert_equal "John Doe", body end it "exposes nested params with indifferent hash" do mock_app { get '/testme' do assert_equal 'baz', params['bar']['foo'] assert_equal 'baz', params['bar'][:foo] 'well, alright' end } get '/testme?bar[foo]=baz' assert_equal 'well, alright', body end it "exposes params nested within arrays with indifferent hash" do mock_app { get '/testme' do assert_equal 'baz', params['bar'][0]['foo'] assert_equal 'baz', params['bar'][0][:foo] 'well, alright' end } get '/testme?bar[][foo]=baz' assert_equal 'well, alright', body end it "supports arrays within params" do mock_app { get '/foo' do assert_equal ['A', 'B'], params['bar'] 'looks good' end } get '/foo?bar[]=A&bar[]=B' assert ok? assert_equal 'looks good', body end it "supports deeply nested params" do expected_params = { "emacs" => { "map" => { "goto-line" => "M-g g" }, "version" => "22.3.1" }, "browser" => { "firefox" => {"engine" => {"name"=>"spidermonkey", "version"=>"1.7.0"}}, "chrome" => {"engine" => {"name"=>"V8", "version"=>"1.0"}} }, "paste" => {"name"=>"hello world", "syntax"=>"ruby"} } mock_app { get '/foo' do assert_equal expected_params, params 'looks good' end } get '/foo', expected_params assert ok? assert_equal 'looks good', body end it "preserves non-nested params" do mock_app { get '/foo' do assert_equal "2", params["article_id"] assert_equal "awesome", params['comment']['body'] assert_nil params['comment[body]'] 'looks good' end } get '/foo?article_id=2&comment[body]=awesome' assert ok? assert_equal 'looks good', body end it "matches paths that include spaces encoded with %20" do mock_app { get '/path with spaces' do 'looks good' end } get '/path%20with%20spaces' assert ok? assert_equal 'looks good', body end it "matches paths that include spaces encoded with +" do mock_app { get '/path with spaces' do 'looks good' end } get '/path+with+spaces' assert ok? assert_equal 'looks good', body end it "matches paths that include ampersands" do mock_app { get '/:name' do 'looks good' end } get '/foo&bar' assert ok? assert_equal 'looks good', body end it "URL decodes named parameters and splats" do mock_app { get '/:foo/*' do assert_equal 'hello world', params['foo'] assert_equal ['how are you'], params['splat'] nil end } get '/hello%20world/how%20are%20you' assert ok? end it 'unescapes named parameters and splats' do mock_app { get '/:foo/*' do |a, b| assert_equal "foo\xE2\x80\x8Cbar", params['foo'] assert_predicate params['foo'], :valid_encoding? assert_equal ["bar\xE2\x80\x8Cbaz"], params['splat'] end } get '/foo%e2%80%8cbar/bar%e2%80%8cbaz' assert ok? end it 'supports regular expressions' do mock_app { get(/\/foo...\/bar/) do 'Hello World' end } get '/foooom/bar' assert ok? assert_equal 'Hello World', body end it 'unescapes regular expression captures' do mock_app { get(/\/foo\/(.+)/) do |path| path end } get '/foo/bar%e2%80%8cbaz' assert ok? assert_equal "bar\xE2\x80\x8Cbaz", body assert_predicate body, :valid_encoding? end it 'makes regular expression captures available in params[:captures]' do mock_app { get(/\/fo(.*)\/ba(.*)/) do assert_equal ['orooomma', 'f'], params[:captures] 'right on' end } get '/foorooomma/baf' assert ok? assert_equal 'right on', body end it 'makes regular expression captures available in params[:captures] for concatenated routes' do with_regexp = Mustermann.new('/prefix') + Mustermann.new("/fo(.*)/ba(.*)", type: :regexp) without_regexp = Mustermann.new('/prefix', type: :identity) + Mustermann.new('/baz') mock_app { get(with_regexp) do assert_equal ['orooomma', 'f'], params[:captures] 'right on' end get(without_regexp) do assert !params.keys.include?(:captures) 'no captures here' end } get '/prefix/foorooomma/baf' assert ok? assert_equal 'right on', body get '/prefix/baz' assert ok? assert_equal 'no captures here', body end it 'supports regular expression look-alike routes' do mock_app { get(PatternLookAlike.new) do assert_equal 'this', params[:one] assert_equal 'is', params[:two] assert_equal 'a', params[:three] assert_equal 'test', params[:four] 'right on' end } get '/this/is/a/test/' assert ok? assert_equal 'right on', body end it 'raises a TypeError when pattern is not a String or Regexp' do assert_raises(TypeError) { mock_app { get(42){} } } end it "returns response immediately on halt" do mock_app { get '/' do halt 'Hello World' 'Boo-hoo World' end } get '/' assert ok? assert_equal 'Hello World', body end it "halts with a response tuple" do mock_app { get '/' do halt 295, {'Content-Type' => 'text/plain'}, 'Hello World' end } get '/' assert_equal 295, status assert_equal 'text/plain', response['Content-Type'] assert_equal 'Hello World', body end it "halts with an array of strings" do mock_app { get '/' do halt %w[Hello World How Are You] end } get '/' assert_equal 'HelloWorldHowAreYou', body end it 'sets response.status with halt' do status_was = nil mock_app do after { status_was = status } get('/') { halt 500, 'error' } end get '/' assert_status 500 assert_equal 500, status_was end it "transitions to the next matching route on pass" do mock_app { get '/:foo' do pass 'Hello Foo' end get '/*' do assert !params.include?('foo') 'Hello World' end } get '/bar' assert ok? assert_equal 'Hello World', body end it "makes original request params available in error handler" do mock_app { disable :raise_errors get '/:foo' do raise ArgumentError, "foo" end error do "Hello #{params['foo']}2" end } get '/bar' assert_equal 'Hello bar2', body end it "transitions to 404 when passed and no subsequent route matches" do mock_app { get '/:foo' do pass 'Hello Foo' end } get '/bar' assert not_found? end it "transitions to 404 and sets X-Cascade header when passed and no subsequent route matches" do mock_app { get '/:foo' do pass 'Hello Foo' end get '/bar' do 'Hello Bar' end } get '/foo' assert not_found? assert_equal 'pass', response.headers['X-Cascade'] end it "uses optional block passed to pass as route block if no other route is found" do mock_app { get "/" do pass do "this" end "not this" end } get "/" assert ok? assert "this", body end it "uses optional block passed to pass as route block if no other route is found and superclass has non-matching routes" do base = Class.new(Sinatra::Base) base.get('/foo') { 'foo in baseclass' } mock_app(base) { get "/" do pass do "this" end "not this" end } get "/" assert_equal 200, status assert "this", body end it "passes when matching condition returns false" do mock_app { condition { params[:foo] == 'bar' } get '/:foo' do 'Hello World' end } get '/bar' assert ok? assert_equal 'Hello World', body get '/foo' assert not_found? end it "does not pass when matching condition returns nil" do mock_app { condition { nil } get '/:foo' do 'Hello World' end } get '/bar' assert ok? assert_equal 'Hello World', body end it "passes to next route when condition calls pass explicitly" do mock_app { condition { pass unless params[:foo] == 'bar' } get '/:foo' do 'Hello World' end } get '/bar' assert ok? assert_equal 'Hello World', body get '/foo' assert not_found? end it "passes to the next route when host_name does not match" do mock_app { host_name 'example.com' get '/foo' do 'Hello World' end } get '/foo' assert not_found? get '/foo', {}, { 'HTTP_HOST' => 'example.com' } assert_equal 200, status assert_equal 'Hello World', body end it "passes to the next route when user_agent does not match" do mock_app { user_agent(/Foo/) get '/foo' do 'Hello World' end } get '/foo' assert not_found? get '/foo', {}, { 'HTTP_USER_AGENT' => 'Foo Bar' } assert_equal 200, status assert_equal 'Hello World', body end it "treats missing user agent like an empty string" do mock_app do user_agent(/.*/) get '/' do "Hello World" end end get '/' assert_equal 200, status assert_equal 'Hello World', body end it "makes captures in user agent pattern available in params[:agent]" do mock_app { user_agent(/Foo (.*)/) get '/foo' do 'Hello ' + params[:agent].first end } get '/foo', {}, { 'HTTP_USER_AGENT' => 'Foo Bar' } assert_equal 200, status assert_equal 'Hello Bar', body end it 'matches mime_types with dots, hyphens and plus signs' do mime_types = %w( application/atom+xml application/ecmascript application/EDI-X12 application/EDIFACT application/json application/javascript application/octet-stream application/ogg application/pdf application/postscript application/rdf+xml application/rss+xml application/soap+xml application/font-woff application/xhtml+xml application/xml application/xml-dtd application/xop+xml application/zip application/gzip audio/basic audio/L24 audio/mp4 audio/mpeg audio/ogg audio/vorbis audio/vnd.rn-realaudio audio/vnd.wave audio/webm image/gif image/jpeg image/pjpeg image/png image/svg+xml image/tiff image/vnd.microsoft.icon message/http message/imdn+xml message/partial message/rfc822 model/example model/iges model/mesh model/vrml model/x3d+binary model/x3d+vrml model/x3d+xml multipart/mixed multipart/alternative multipart/related multipart/form-data multipart/signed multipart/encrypted text/cmd text/css text/csv text/html text/javascript application/javascript text/plain text/vcard text/xml video/mpeg video/mp4 video/ogg video/quicktime video/webm video/x-matroska video/x-ms-wmv video/x-flv application/vnd.oasis.opendocument.text application/vnd.oasis.opendocument.spreadsheet application/vnd.oasis.opendocument.presentation application/vnd.oasis.opendocument.graphics application/vnd.ms-excel application/vnd.openxmlformats-officedocument.spreadsheetml.sheet application/vnd.ms-powerpoint application/vnd.openxmlformats-officedocument.presentationml.presentation application/vnd.openxmlformats-officedocument.wordprocessingml.document application/vnd.mozilla.xul+xml application/vnd.google-earth.kml+xml application/x-deb application/x-dvi application/x-font-ttf application/x-javascript application/x-latex application/x-mpegURL application/x-rar-compressed application/x-shockwave-flash application/x-stuffit application/x-tar application/x-www-form-urlencoded application/x-xpinstall audio/x-aac audio/x-caf image/x-xcf text/x-gwt-rpc text/x-jquery-tmpl application/x-pkcs12 application/x-pkcs12 application/x-pkcs7-certificates application/x-pkcs7-certificates application/x-pkcs7-certreqresp application/x-pkcs7-mime application/x-pkcs7-mime application/x-pkcs7-signature ) mime_types.each { |mime_type| assert mime_type.match(Sinatra::Request::HEADER_VALUE_WITH_PARAMS) } end it "filters by accept header" do mock_app { get '/', :provides => :xml do env['HTTP_ACCEPT'] end get '/foo', :provides => :html do env['HTTP_ACCEPT'] end get '/stream', :provides => 'text/event-stream' do env['HTTP_ACCEPT'] end } get '/', {}, { 'HTTP_ACCEPT' => 'application/xml' } assert ok? assert_equal 'application/xml', body assert_equal 'application/xml;charset=utf-8', response.headers['Content-Type'] get '/', {}, {} assert ok? assert_equal '', body assert_equal 'application/xml;charset=utf-8', response.headers['Content-Type'] get '/', {}, { 'HTTP_ACCEPT' => '*/*' } assert ok? assert_equal '*/*', body assert_equal 'application/xml;charset=utf-8', response.headers['Content-Type'] get '/', {}, { 'HTTP_ACCEPT' => 'text/html;q=0.9' } assert !ok? get '/foo', {}, { 'HTTP_ACCEPT' => 'text/html;q=0.9' } assert ok? assert_equal 'text/html;q=0.9', body get '/foo', {}, { 'HTTP_ACCEPT' => '' } assert ok? assert_equal '', body get '/foo', {}, { 'HTTP_ACCEPT' => '*/*' } assert ok? assert_equal '*/*', body get '/foo', {}, { 'HTTP_ACCEPT' => 'application/xml' } assert !ok? get '/stream', {}, { 'HTTP_ACCEPT' => 'text/event-stream' } assert ok? assert_equal 'text/event-stream', body get '/stream', {}, { 'HTTP_ACCEPT' => '' } assert ok? assert_equal '', body get '/stream', {}, { 'HTTP_ACCEPT' => '*/*' } assert ok? assert_equal '*/*', body get '/stream', {}, { 'HTTP_ACCEPT' => 'application/xml' } assert !ok? end it "filters by current Content-Type" do mock_app do before('/txt') { content_type :txt } get('*', :provides => :txt) { 'txt' } before('/html') { content_type :html } get('*', :provides => :html) { 'html' } end get '/', {}, { 'HTTP_ACCEPT' => '*/*' } assert ok? assert_equal 'text/plain;charset=utf-8', response.headers['Content-Type'] assert_body 'txt' get '/txt', {}, { 'HTTP_ACCEPT' => 'text/plain' } assert ok? assert_equal 'text/plain;charset=utf-8', response.headers['Content-Type'] assert_body 'txt' get '/', {}, { 'HTTP_ACCEPT' => 'text/html' } assert ok? assert_equal 'text/html;charset=utf-8', response.headers['Content-Type'] assert_body 'html' end it "allows multiple mime types for accept header" do types = ['image/jpeg', 'image/pjpeg'] mock_app { get '/', :provides => types do env['HTTP_ACCEPT'] end } types.each do |type| get '/', {}, { 'HTTP_ACCEPT' => type } assert ok? assert_equal type, body assert_equal type, response.headers['Content-Type'] end end it 'respects user agent preferences for the content type' do mock_app { get('/', :provides => [:png, :html]) { content_type }} get '/', {}, { 'HTTP_ACCEPT' => 'image/png;q=0.5,text/html;q=0.8' } assert_body 'text/html;charset=utf-8' get '/', {}, { 'HTTP_ACCEPT' => 'image/png;q=0.8,text/html;q=0.5' } assert_body 'image/png' end it 'accepts generic types' do mock_app do get('/', :provides => :xml) { content_type } get('/') { 'no match' } end get '/', {}, { 'HTTP_ACCEPT' => 'foo/*' } assert_body 'no match' get '/', {}, { 'HTTP_ACCEPT' => 'application/*' } assert_body 'application/xml;charset=utf-8' get '/', {}, { 'HTTP_ACCEPT' => '*/*' } assert_body 'application/xml;charset=utf-8' end it 'prefers concrete over partly generic types' do mock_app { get('/', :provides => [:png, :html]) { content_type }} get '/', {}, { 'HTTP_ACCEPT' => 'image/*, text/html' } assert_body 'text/html;charset=utf-8' get '/', {}, { 'HTTP_ACCEPT' => 'image/png, text/*' } assert_body 'image/png' end it 'prefers concrete over fully generic types' do mock_app { get('/', :provides => [:png, :html]) { content_type }} get '/', {}, { 'HTTP_ACCEPT' => '*/*, text/html' } assert_body 'text/html;charset=utf-8' get '/', {}, { 'HTTP_ACCEPT' => 'image/png, */*' } assert_body 'image/png' end it 'prefers partly generic over fully generic types' do mock_app { get('/', :provides => [:png, :html]) { content_type }} get '/', {}, { 'HTTP_ACCEPT' => '*/*, text/*' } assert_body 'text/html;charset=utf-8' get '/', {}, { 'HTTP_ACCEPT' => 'image/*, */*' } assert_body 'image/png' end it 'respects quality with generic types' do mock_app { get('/', :provides => [:png, :html]) { content_type }} get '/', {}, { 'HTTP_ACCEPT' => 'image/*;q=1, text/html;q=0' } assert_body 'image/png' get '/', {}, { 'HTTP_ACCEPT' => 'image/png;q=0.5, text/*;q=0.7' } assert_body 'text/html;charset=utf-8' end it 'supplies a default quality of 1.0' do mock_app { get('/', :provides => [:png, :html]) { content_type }} get '/', {}, { 'HTTP_ACCEPT' => 'image/png;q=0.5, text/*' } assert_body 'text/html;charset=utf-8' end it 'orders types with equal quality by parameter count' do mock_app do get('/', :provides => [:png, :jpg]) { content_type } end lo_png = 'image/png;q=0.5' hi_png = 'image/png;q=0.5;profile=FOGRA40;gamma=0.8' jpeg = 'image/jpeg;q=0.5;compress=0.25' get '/', {}, { 'HTTP_ACCEPT' => "#{lo_png}, #{jpeg}" } assert_body 'image/jpeg' get '/', {}, { 'HTTP_ACCEPT' => "#{hi_png}, #{jpeg}" } assert_body 'image/png' end it 'ignores the quality parameter when ordering by parameter count' do mock_app do get('/', :provides => [:png, :jpg]) { content_type } end lo_png = 'image/png' hi_png = 'image/png;profile=FOGRA40;gamma=0.8' jpeg = 'image/jpeg;q=1.0;compress=0.25' get '/', {}, { 'HTTP_ACCEPT' => "#{jpeg}, #{lo_png}" } assert_body 'image/jpeg' get '/', {}, { 'HTTP_ACCEPT' => "#{jpeg}, #{hi_png}" } assert_body 'image/png' end it 'properly handles quoted strings in parameters' do mock_app do get('/', :provides => [:png, :jpg]) { content_type } end get '/', {}, { 'HTTP_ACCEPT' => 'image/png;q=0.5;profile=",image/jpeg,"' } assert_body 'image/png' get '/', {}, { 'HTTP_ACCEPT' => 'image/png;q=0.5,image/jpeg;q=0;x=";q=1.0"' } assert_body 'image/png' get '/', {}, { 'HTTP_ACCEPT' => 'image/png;q=0.5,image/jpeg;q=0;x="\";q=1.0"' } assert_body 'image/png' end it 'accepts both text/javascript and application/javascript for js' do mock_app { get('/', :provides => :js) { content_type }} get '/', {}, { 'HTTP_ACCEPT' => 'application/javascript' } assert_body 'application/javascript;charset=utf-8' get '/', {}, { 'HTTP_ACCEPT' => 'text/javascript' } assert_body 'text/javascript;charset=utf-8' end it 'accepts both text/xml and application/xml for xml' do mock_app { get('/', :provides => :xml) { content_type }} get '/', {}, { 'HTTP_ACCEPT' => 'application/xml' } assert_body 'application/xml;charset=utf-8' get '/', {}, { 'HTTP_ACCEPT' => 'text/xml' } assert_body 'text/xml;charset=utf-8' end it 'passes a single url param as block parameters when one param is specified' do mock_app { get '/:foo' do |foo| assert_equal 'bar', foo end } get '/bar' assert ok? end it 'passes multiple params as block parameters when many are specified' do mock_app { get '/:foo/:bar/:baz' do |foo, bar, baz| assert_equal 'abc', foo assert_equal 'def', bar assert_equal 'ghi', baz end } get '/abc/def/ghi' assert ok? end it 'passes regular expression captures as block parameters' do mock_app { get(/\/fo(.*)\/ba(.*)/) do |foo, bar| assert_equal 'orooomma', foo assert_equal 'f', bar 'looks good' end } get '/foorooomma/baf' assert ok? assert_equal 'looks good', body end it "supports mixing multiple splat params like /*/foo/*/* as block parameters" do mock_app { get '/*/foo/*/*' do |foo, bar, baz| assert_equal 'bar', foo assert_equal 'bling', bar assert_equal 'baz/boom', baz 'looks good' end } get '/bar/foo/bling/baz/boom' assert ok? assert_equal 'looks good', body end it "uses the default encoding for block parameters" do mock_app { set :default_encoding ,'ISO-8859-1' get '/:foo/:bar' do |foo, bar| "foo=#{foo.encoding};bar=#{bar.encoding}" end } get '/f%C3%B6%C3%B6/b%C3%B6%C3%B6' assert ok? assert_equal 'foo=ISO-8859-1;bar=ISO-8859-1', body end it 'raises an ArgumentError with block arity > 1 and too many values' do mock_app do get '/:foo/:bar/:baz' do |foo, bar| 'quux' end end assert_raises(ArgumentError) { get '/a/b/c' } end it 'raises an ArgumentError with block param arity > 1 and too few values' do mock_app { get '/:foo/:bar' do |foo, bar, baz| 'quux' end } assert_raises(ArgumentError) { get '/a/b' } end it 'succeeds if no block parameters are specified' do mock_app { get '/:foo/:bar' do 'quux' end } get '/a/b' assert ok? assert_equal 'quux', body end it 'passes all params with block param arity -1 (splat args)' do mock_app { get '/:foo/:bar' do |*args| args.join end } get '/a/b' assert ok? assert_equal 'ab', body end it 'allows custom route-conditions to be set via route options' do protector = Module.new { def protect(*args) condition { unless authorize(params["user"], params["password"]) halt 403, "go away" end } end } mock_app { register protector helpers do def authorize(username, password) username == "foo" && password == "bar" end end get "/", :protect => true do "hey" end } get "/" assert forbidden? assert_equal "go away", body get "/", :user => "foo", :password => "bar" assert ok? assert_equal "hey", body end it 'raises an ArgumentError with block param arity 1 and no values' do mock_app { get '/foo' do |foo| 'quux' end } assert_raises(ArgumentError) { get '/foo' } end it 'raises an ArgumentError with block param arity 1 and too many values' do mock_app { get '/:foo/:bar/:baz' do |foo| 'quux' end } assert_raises(ArgumentError) { get '/a/b/c' } end it "matches routes defined in superclasses" do base = Class.new(Sinatra::Base) base.get('/foo') { 'foo in baseclass' } mock_app(base) { get('/bar') { 'bar in subclass' } } get '/foo' assert ok? assert_equal 'foo in baseclass', body get '/bar' assert ok? assert_equal 'bar in subclass', body end it "matches routes in subclasses before superclasses" do base = Class.new(Sinatra::Base) base.get('/foo') { 'foo in baseclass' } base.get('/bar') { 'bar in baseclass' } mock_app(base) { get('/foo') { 'foo in subclass' } } get '/foo' assert ok? assert_equal 'foo in subclass', body get '/bar' assert ok? assert_equal 'bar in baseclass', body end it "adds hostname condition when it is in options" do mock_app { get '/foo', :host => 'host' do 'foo' end } get '/foo' assert not_found? end it 'allows using call to fire another request internally' do mock_app do get '/foo' do status, headers, body = call env.merge("PATH_INFO" => '/bar') [status, headers, body.each.map(&:upcase)] end get '/bar' do "bar" end end get '/foo' assert ok? assert_body "BAR" end it 'plays well with other routing middleware' do middleware = Sinatra.new inner_app = Sinatra.new { get('/foo') { 'hello' } } builder = Rack::Builder.new do use middleware map('/test') { run inner_app } end @app = builder.to_app get '/test/foo' assert ok? assert_body 'hello' end it 'returns the route signature' do signature = list = nil mock_app do signature = post('/') { } list = routes['POST'] end assert_equal Array, signature.class assert_equal 3, signature.length assert list.include?(signature) end it "sets env['sinatra.route'] to the matched route" do mock_app do after do assert_equal 'GET /users/:id/status', env['sinatra.route'] end get('/users/:id/status') { 'ok' } end get '/users/1/status' end it 'treats routes with and without trailing slashes differently' do mock_app do get '/foo' do 'Foo' end get '/foo/' do 'Foo with a slash' end end get '/foo' assert_equal 'Foo', body refute_equal 'Foo with a slash', body get '/foo/' assert_equal 'Foo with a slash', body end it 'does not treat routes with and without trailing slashes differently if :strict_paths is disabled' do mock_app do disable :strict_paths get '/foo' do 'foo' end end get '/foo' assert_equal 'foo', body get '/foo/' assert_equal 'foo', body end end sinatra-2.0.8.1/test/sass_test.rb000066400000000000000000000063511360317524000166430ustar00rootroot00000000000000require File.expand_path('../helper', __FILE__) begin require 'sass' class SassTest < Minitest::Test def sass_app(options = {}, &block) mock_app do set :views, File.dirname(__FILE__) + '/views' set options get('/', &block) end get '/' end it 'renders inline Sass strings' do sass_app { sass "#sass\n background-color: white\n" } assert ok? assert_equal "#sass {\n background-color: white; }\n", body end it 'defaults content type to css' do sass_app { sass "#sass\n background-color: white\n" } assert ok? assert_equal "text/css;charset=utf-8", response['Content-Type'] end it 'defaults allows setting content type per route' do sass_app do content_type :html sass "#sass\n background-color: white\n" end assert ok? assert_equal "text/html;charset=utf-8", response['Content-Type'] end it 'defaults allows setting content type globally' do sass_app(:sass => { :content_type => 'html' }) { sass "#sass\n background-color: white\n" } assert ok? assert_equal "text/html;charset=utf-8", response['Content-Type'] end it 'renders .sass files in views path' do sass_app { sass :hello } assert ok? assert_equal "#sass {\n background-color: white; }\n", body end it 'ignores the layout option' do sass_app { sass :hello, :layout => :layout2 } assert ok? assert_equal "#sass {\n background-color: white; }\n", body end it "raises error if template not found" do mock_app { get('/') { sass :no_such_template } } assert_raises(Errno::ENOENT) { get('/') } end it "passes SASS options to the Sass engine" do sass_app do sass( "#sass\n background-color: white\n color: black\n", :style => :compact ) end assert ok? assert_equal("#sass { background-color: white; color: black; }\n", body) end it "passes default SASS options to the Sass engine" do mock_app do set :sass, {:style => :compact} # default Sass style is :nested get('/') { sass("#sass\n background-color: white\n color: black\n") } end get '/' assert ok? assert_equal "#sass { background-color: white; color: black; }\n", body end it "merges the default SASS options with the overrides" do mock_app do # default Sass attribute_syntax is :normal (with : in front) set :sass, {:style => :compact, :attribute_syntax => :alternate } get('/') { sass("#sass\n background-color: white\n color: black\n") } get('/raised') do # retains global attribute_syntax settings sass( "#sass\n :background-color white\n :color black\n", :style => :expanded ) end get('/expanded_normal') do sass( "#sass\n :background-color white\n :color black\n", :style => :expanded, :attribute_syntax => :normal ) end end get '/' assert ok? assert_equal "#sass { background-color: white; color: black; }\n", body assert_raises(Sass::SyntaxError) { get('/raised') } get '/expanded_normal' assert ok? assert_equal "#sass {\n background-color: white;\n color: black;\n}\n", body end end rescue LoadError warn "#{$!}: skipping sass tests" end sinatra-2.0.8.1/test/scss_test.rb000066400000000000000000000044631360317524000166470ustar00rootroot00000000000000require File.expand_path('../helper', __FILE__) begin require 'sass' class ScssTest < Minitest::Test def scss_app(options = {}, &block) mock_app do set :views, File.dirname(__FILE__) + '/views' set options get('/', &block) end get '/' end it 'renders inline Scss strings' do scss_app { scss "#scss {\n background-color: white; }\n" } assert ok? assert_equal "#scss {\n background-color: white; }\n", body end it 'defaults content type to css' do scss_app { scss "#scss {\n background-color: white; }\n" } assert ok? assert_equal "text/css;charset=utf-8", response['Content-Type'] end it 'defaults allows setting content type per route' do scss_app do content_type :html scss "#scss {\n background-color: white; }\n" end assert ok? assert_equal "text/html;charset=utf-8", response['Content-Type'] end it 'defaults allows setting content type globally' do scss_app(:scss => { :content_type => 'html' }) { scss "#scss {\n background-color: white; }\n" } assert ok? assert_equal "text/html;charset=utf-8", response['Content-Type'] end it 'renders .scss files in views path' do scss_app { scss :hello } assert ok? assert_equal "#scss {\n background-color: white; }\n", body end it 'ignores the layout option' do scss_app { scss :hello, :layout => :layout2 } assert ok? assert_equal "#scss {\n background-color: white; }\n", body end it "raises error if template not found" do mock_app { get('/') { scss(:no_such_template) } } assert_raises(Errno::ENOENT) { get('/') } end it "passes scss options to the scss engine" do scss_app do scss( "#scss {\n background-color: white;\n color: black\n}", :style => :compact ) end assert ok? assert_equal "#scss { background-color: white; color: black; }\n", body end it "passes default scss options to the scss engine" do mock_app do set :scss, {:style => :compact} # default scss style is :nested get('/') { scss("#scss {\n background-color: white;\n color: black;\n}") } end get '/' assert ok? assert_equal "#scss { background-color: white; color: black; }\n", body end end rescue LoadError warn "#{$!}: skipping scss tests" end sinatra-2.0.8.1/test/server_test.rb000066400000000000000000000035621360317524000172010ustar00rootroot00000000000000require File.expand_path('../helper', __FILE__) require 'stringio' module Rack::Handler class Mock extend Minitest::Assertions # Allow assertions in request context def self.assertions @assertions ||= 0 end def self.assertions= assertions @assertions = assertions end def self.run(app, options={}) assert(app < Sinatra::Base) assert_equal 9001, options[:Port] assert_equal 'foo.local', options[:Host] yield new end def stop end end register 'mock', 'Rack::Handler::Mock' end class ServerTest < Minitest::Test setup do mock_app do set :server, 'mock' set :bind, 'foo.local' set :port, 9001 end $stderr = StringIO.new end def teardown $stderr = STDERR end it "locates the appropriate Rack handler and calls ::run" do @app.run! end it "sets options on the app before running" do @app.run! :sessions => true assert @app.sessions? end it "falls back on the next server handler when not found" do @app.run! :server => %w[foo bar mock] end it "initializes Rack middleware immediately on server run" do class MyMiddleware @@initialized = false def initialize(app) @@initialized = true end def self.initialized @@initialized end def call(env) end end @app.use MyMiddleware assert_equal(MyMiddleware.initialized, false) @app.run! assert_equal(MyMiddleware.initialized, true) end describe "Quiet mode" do it "sends data to stderr when server starts and stops" do @app.run! assert_match(/\=\= Sinatra/, $stderr.string) end context "when quiet mode is activated" do it "does not generate Sinatra start and stop messages" do @app.run! quiet: true refute_match(/\=\= Sinatra/, $stderr.string) end end end end sinatra-2.0.8.1/test/settings_test.rb000066400000000000000000000375721360317524000175430ustar00rootroot00000000000000require File.expand_path('../helper', __FILE__) class SettingsTest < Minitest::Test setup do @base = Sinatra.new(Sinatra::Base) @base.set :environment => :foo, :app_file => nil @application = Sinatra.new(Sinatra::Application) @application.set :environment => :foo, :app_file => nil end it 'sets settings to literal values' do @base.set(:foo, 'bar') assert @base.respond_to?(:foo) assert_equal 'bar', @base.foo end it 'sets settings to Procs' do @base.set(:foo, Proc.new { 'baz' }) assert @base.respond_to?(:foo) assert_equal 'baz', @base.foo end it 'sets settings using a block' do @base.set(:foo){ 'baz' } assert @base.respond_to?(:foo) assert_equal 'baz', @base.foo end it 'raises an error with a value and a block' do assert_raises ArgumentError do @base.set(:fiz, 'boom!'){ 'baz' } end assert !@base.respond_to?(:fiz) end it 'raises an error without value and block' do assert_raises(ArgumentError) { @base.set(:fiz) } assert !@base.respond_to?(:fiz) end it 'allows setting a value to the app class' do @base.set :base, @base assert @base.respond_to?(:base) assert_equal @base, @base.base end it 'raises an error with the app class as value and a block' do assert_raises ArgumentError do @base.set(:fiz, @base) { 'baz' } end assert !@base.respond_to?(:fiz) end it "sets multiple settings with a Hash" do @base.set :foo => 1234, :bar => 'Hello World', :baz => Proc.new { 'bizzle' } assert_equal 1234, @base.foo assert_equal 'Hello World', @base.bar assert_equal 'bizzle', @base.baz end it 'sets multiple settings using #each' do @base.set [["foo", "bar"]] assert_equal "bar", @base.foo end it 'inherits settings methods when subclassed' do @base.set :foo, 'bar' @base.set :biz, Proc.new { 'baz' } sub = Class.new(@base) assert sub.respond_to?(:foo) assert_equal 'bar', sub.foo assert sub.respond_to?(:biz) assert_equal 'baz', sub.biz end it 'overrides settings in subclass' do @base.set :foo, 'bar' @base.set :biz, Proc.new { 'baz' } sub = Class.new(@base) sub.set :foo, 'bling' assert_equal 'bling', sub.foo assert_equal 'bar', @base.foo end it 'creates setter methods when first defined' do @base.set :foo, 'bar' assert @base.respond_to?('foo=') @base.foo = 'biz' assert_equal 'biz', @base.foo end it 'creates predicate methods when first defined' do @base.set :foo, 'hello world' assert @base.respond_to?(:foo?) assert @base.foo? @base.set :foo, nil assert !@base.foo? end it 'uses existing setter methods if detected' do class << @base def foo @foo end def foo=(value) @foo = 'oops' end end @base.set :foo, 'bam' assert_equal 'oops', @base.foo end it 'merges values of multiple set calls if those are hashes' do @base.set :foo, :a => 1 sub = Class.new(@base) sub.set :foo, :b => 2 assert_equal({:a => 1, :b => 2}, sub.foo) end it 'merging does not affect the superclass' do @base.set :foo, :a => 1 sub = Class.new(@base) sub.set :foo, :b => 2 assert_equal({:a => 1}, @base.foo) end it 'is possible to change a value from a hash to something else' do @base.set :foo, :a => 1 @base.set :foo, :bar assert_equal(:bar, @base.foo) end it 'merges values with values of the superclass if those are hashes' do @base.set :foo, :a => 1 @base.set :foo, :b => 2 assert_equal({:a => 1, :b => 2}, @base.foo) end it "sets multiple settings to true with #enable" do @base.enable :sessions, :foo, :bar assert @base.sessions assert @base.foo assert @base.bar end it "sets multiple settings to false with #disable" do @base.disable :sessions, :foo, :bar assert !@base.sessions assert !@base.foo assert !@base.bar end it 'is accessible from instances via #settings' do assert_equal :foo, @base.new!.settings.environment end it 'is accessible from class via #settings' do assert_equal :foo, @base.settings.environment end describe 'methodoverride' do it 'is disabled on Base' do assert ! @base.method_override? end it 'is enabled on Application' do assert @application.method_override? end it 'enables MethodOverride middleware' do @base.set :method_override, true @base.put('/') { 'okay' } @app = @base post '/', {'_method'=>'PUT'}, {} assert_equal 200, status assert_equal 'okay', body end it 'is backward compatible with methodoverride' do assert ! @base.methodoverride? @base.enable :methodoverride assert @base.methodoverride? end it 'ignores bundler/inline from callers' do @application.stub(:caller, ->(_){ ['/path/to/bundler/inline.rb', $0] }) do assert_equal File.expand_path($0), File.expand_path(@application.send(:caller_files).first) end end end describe 'run' do it 'is disabled on Base' do assert ! @base.run? end it 'is enabled on Application except in test environment' do assert @application.run? @application.set :environment, :test assert ! @application.run? end end describe 'raise_errors' do it 'is enabled on Base only in test' do assert ! @base.raise_errors? @base.set(:environment, :test) assert @base.raise_errors? end it 'is enabled on Application only in test' do assert ! @application.raise_errors? @application.set(:environment, :test) assert @application.raise_errors? end end describe 'show_exceptions' do it 'is disabled on Base except under development' do assert ! @base.show_exceptions? @base.environment = :development assert @base.show_exceptions? end it 'is disabled on Application except in development' do assert ! @application.show_exceptions? @application.set(:environment, :development) assert @application.show_exceptions? end it 'returns a friendly 500' do klass = Sinatra.new(Sinatra::Application) mock_app(klass) { enable :show_exceptions get '/' do raise StandardError end } get '/' assert_equal 500, status assert body.include?("StandardError") assert body.include?("show_exceptions setting") end it 'does not attempt to show unparseable query parameters' do klass = Sinatra.new(Sinatra::Application) mock_app(klass) { enable :show_exceptions get '/' do raise Sinatra::BadRequest end } get '/' assert_equal 400, status refute body.include?('
') refute body.include?('
') end it 'does not override app-specified error handling when set to :after_handler' do ran = false mock_app do set :show_exceptions, :after_handler error(RuntimeError) { ran = true } get('/') { raise RuntimeError } end get '/' assert_equal 500, status assert ran end it 'does catch any other exceptions when set to :after_handler' do ran = false mock_app do set :show_exceptions, :after_handler error(RuntimeError) { ran = true } get('/') { raise ArgumentError } end get '/' assert_equal 500, status assert !ran end end describe 'dump_errors' do it 'is disabled on Base in test' do @base.environment = :test assert ! @base.dump_errors? @base.environment = :development assert @base.dump_errors? @base.environment = :production assert @base.dump_errors? end it 'dumps exception with backtrace to rack.errors' do klass = Sinatra.new(Sinatra::Application) mock_app(klass) { enable :dump_errors disable :raise_errors error do error = @env['rack.errors'].instance_variable_get(:@error) error.rewind error.read end get '/' do raise end } get '/' assert body.include?("RuntimeError") && body.include?("settings_test.rb") end it 'does not dump 404 errors' do klass = Sinatra.new(Sinatra::Application) mock_app(klass) { enable :dump_errors disable :raise_errors error do error = @env['rack.errors'].instance_variable_get(:@error) error.rewind error.read end get '/' do raise Sinatra::NotFound end } get '/' assert !body.include?("NotFound") && !body.include?("settings_test.rb") end end describe 'sessions' do it 'is disabled on Base' do assert ! @base.sessions? end it 'is disabled on Application' do assert ! @application.sessions? end end describe 'logging' do it 'is disabled on Base' do assert ! @base.logging? end it 'is enabled on Application except in test environment' do assert @application.logging? @application.set :environment, :test assert ! @application.logging end end describe 'static' do it 'is disabled on Base by default' do assert ! @base.static? end it 'is enabled on Base when public_folder is set and exists' do @base.set :environment, :development @base.set :public_folder, File.dirname(__FILE__) assert @base.static? end it 'is enabled on Base when root is set and root/public_folder exists' do @base.set :environment, :development @base.set :root, File.dirname(__FILE__) assert @base.static? end it 'is disabled on Application by default' do assert ! @application.static? end it 'is enabled on Application when public_folder is set and exists' do @application.set :environment, :development @application.set :public_folder, File.dirname(__FILE__) assert @application.static? end it 'is enabled on Application when root is set and root/public_folder exists' do @application.set :environment, :development @application.set :root, File.dirname(__FILE__) assert @application.static? end it 'is possible to use Module#public' do @base.send(:define_method, :foo) { } @base.send(:private, :foo) assert !@base.public_method_defined?(:foo) @base.send(:public, :foo) assert @base.public_method_defined?(:foo) end it 'is possible to use the keyword public in a sinatra app' do app = Sinatra.new do private def priv; end public def pub; end end assert !app.public_method_defined?(:priv) assert app.public_method_defined?(:pub) end end describe 'bind' do it 'defaults to 0.0.0.0' do assert_equal '0.0.0.0', @base.bind assert_equal '0.0.0.0', @application.bind end end describe 'port' do it 'defaults to 4567' do assert_equal 4567, @base.port assert_equal 4567, @application.port end end describe 'server' do it 'includes webrick' do assert @base.server.include?('webrick') assert @application.server.include?('webrick') end it 'includes puma' do assert @base.server.include?('puma') assert @application.server.include?('puma') end it 'includes thin' do next if RUBY_ENGINE == 'jruby' assert @base.server.include?('thin') assert @application.server.include?('thin') end end describe 'app_file' do it 'is nil for base classes' do assert_nil Sinatra::Base.app_file assert_nil Sinatra::Application.app_file end it 'defaults to the file subclassing' do assert_equal File.expand_path(__FILE__), Sinatra.new.app_file end end describe 'root' do it 'is nil if app_file is not set' do assert_nil @base.root assert_nil @application.root end it 'is equal to the expanded basename of app_file' do @base.app_file = __FILE__ assert_equal File.expand_path(File.dirname(__FILE__)), @base.root @application.app_file = __FILE__ assert_equal File.expand_path(File.dirname(__FILE__)), @application.root end end describe 'views' do it 'is nil if root is not set' do assert_nil @base.views assert_nil @application.views end it 'is set to root joined with views/' do @base.root = File.dirname(__FILE__) assert_equal File.dirname(__FILE__) + "/views", @base.views @application.root = File.dirname(__FILE__) assert_equal File.dirname(__FILE__) + "/views", @application.views end end describe 'public_folder' do it 'is nil if root is not set' do assert_nil @base.public_folder assert_nil @application.public_folder end it 'is set to root joined with public/' do @base.root = File.dirname(__FILE__) assert_equal File.dirname(__FILE__) + "/public", @base.public_folder @application.root = File.dirname(__FILE__) assert_equal File.dirname(__FILE__) + "/public", @application.public_folder end end describe 'public_dir' do it 'is an alias for public_folder' do @base.public_dir = File.dirname(__FILE__) assert_equal File.dirname(__FILE__), @base.public_dir assert_equal @base.public_folder, @base.public_dir @application.public_dir = File.dirname(__FILE__) assert_equal File.dirname(__FILE__), @application.public_dir assert_equal @application.public_folder, @application.public_dir end end describe 'lock' do it 'is disabled by default' do assert ! @base.lock? assert ! @application.lock? end end describe 'protection' do class MiddlewareTracker < Rack::Builder def self.track Rack.send :remove_const, :Builder Rack.const_set :Builder, MiddlewareTracker MiddlewareTracker.used.clear yield ensure Rack.send :remove_const, :Builder Rack.const_set :Builder, MiddlewareTracker.superclass end def self.used @used ||= [] end def use(middleware, *) MiddlewareTracker.used << middleware super end end it 'sets up Rack::Protection' do MiddlewareTracker.track do Sinatra::Base.new assert_include MiddlewareTracker.used, Rack::Protection end end it 'sets up Rack::Protection::PathTraversal' do MiddlewareTracker.track do Sinatra::Base.new assert_include MiddlewareTracker.used, Rack::Protection::PathTraversal end end it 'does not set up Rack::Protection::PathTraversal when disabling it' do MiddlewareTracker.track do Sinatra.new { set :protection, :except => :path_traversal }.new assert_include MiddlewareTracker.used, Rack::Protection assert !MiddlewareTracker.used.include?(Rack::Protection::PathTraversal) end end it 'sets up RemoteToken if sessions are enabled' do MiddlewareTracker.track do Sinatra.new { enable :sessions }.new assert_include MiddlewareTracker.used, Rack::Protection::RemoteToken end end it 'sets up RemoteToken if sessions are enabled with a custom session store' do MiddlewareTracker.track do Sinatra.new { enable :sessions set :session_store, Rack::Session::Pool }.new assert_include MiddlewareTracker.used, Rack::Session::Pool assert_include MiddlewareTracker.used, Rack::Protection::RemoteToken end end it 'does not set up RemoteToken if sessions are disabled' do MiddlewareTracker.track do Sinatra.new.new assert !MiddlewareTracker.used.include?(Rack::Protection::RemoteToken) end end it 'sets up RemoteToken if it is configured to' do MiddlewareTracker.track do Sinatra.new { set :protection, :session => true }.new assert_include MiddlewareTracker.used, Rack::Protection::RemoteToken end end end end sinatra-2.0.8.1/test/sinatra_test.rb000066400000000000000000000005461360317524000173330ustar00rootroot00000000000000require File.expand_path('../helper', __FILE__) class SinatraTest < Minitest::Test it 'creates a new Sinatra::Base subclass on new' do app = Sinatra.new { get('/') { 'Hello World' } } assert_same Sinatra::Base, app.superclass end it "responds to #template_cache" do assert_kind_of Tilt::Cache, Sinatra::Base.new!.template_cache end end sinatra-2.0.8.1/test/slim_test.rb000066400000000000000000000051461360317524000166370ustar00rootroot00000000000000require File.expand_path('../helper', __FILE__) begin require 'slim' class SlimTest < Minitest::Test def slim_app(&block) mock_app do set :views, File.dirname(__FILE__) + '/views' get('/', &block) end get '/' end it 'renders inline slim strings' do slim_app { slim "h1 Hiya\n" } assert ok? assert_equal "

Hiya

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

Hello From Slim

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

THIS. IS. SPARTA

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

Slim Layout!

Hello World

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

Title

\n

Subtitle

\n

Contents.

\n" end end rescue LoadError warn "#{$!}: skipping slim tests" end sinatra-2.0.8.1/test/static_test.rb000066400000000000000000000205531360317524000171610ustar00rootroot00000000000000require File.expand_path('../helper', __FILE__) class StaticTest < Minitest::Test setup do mock_app do set :static, true set :public_folder, File.dirname(__FILE__) end end it 'serves GET requests for files in the public directory' do get "/#{File.basename(__FILE__)}" assert ok? assert_equal File.read(__FILE__), body assert_equal File.size(__FILE__).to_s, response['Content-Length'] assert response.headers.include?('Last-Modified') end it 'produces a body that can be iterated over multiple times' do env = Rack::MockRequest.env_for("/#{File.basename(__FILE__)}") _, _, body = @app.call(env) buf1, buf2 = [], [] body.each { |part| buf1 << part } body.each { |part| buf2 << part } assert_equal buf1.join, buf2.join assert_equal File.read(__FILE__), buf1.join end it 'sets the sinatra.static_file env variable if served' do env = Rack::MockRequest.env_for("/#{File.basename(__FILE__)}") @app.call(env) assert_equal File.expand_path(__FILE__), env['sinatra.static_file'] end it 'serves HEAD requests for files in the public directory' do head "/#{File.basename(__FILE__)}" assert ok? assert_equal '', body assert response.headers.include?('Last-Modified') assert_equal File.size(__FILE__).to_s, response['Content-Length'] end %w[POST PUT DELETE].each do |verb| it "does not serve #{verb} requests" do send verb.downcase, "/#{File.basename(__FILE__)}" assert_equal 404, status end end it 'serves files in preference to custom routes' do @app.get("/#{File.basename(__FILE__)}") { 'Hello World' } get "/#{File.basename(__FILE__)}" assert ok? assert body != 'Hello World' end it 'does not serve directories' do get "/" assert not_found? end it 'passes to the next handler when the static option is disabled' do @app.set :static, false get "/#{File.basename(__FILE__)}" assert not_found? end it 'passes to the next handler when the public option is nil' do @app.set :public_folder, nil get "/#{File.basename(__FILE__)}" assert not_found? end it '404s when a file is not found' do get "/foobarbaz.txt" assert not_found? end it 'serves files when .. path traverses within public directory' do get "/data/../#{File.basename(__FILE__)}" assert ok? assert_equal File.read(__FILE__), body end it '404s when .. path traverses outside of public directory' do mock_app do set :static, true set :public_folder, File.dirname(__FILE__) + '/data' end get "/../#{File.basename(__FILE__)}" assert not_found? end def assert_valid_range(http_range, range, path, file) request = Rack::MockRequest.new(@app) response = request.get("/#{File.basename(path)}", 'HTTP_RANGE' => http_range) should_be = file[range] expected_range = "bytes #{range.begin}-#{range.end}/#{file.length}" assert_equal( 206,response.status, "Should be HTTP/1.1 206 Partial content" ) assert_equal( should_be.length, response.body.length, "Unexpected response length for #{http_range}" ) assert_equal( should_be, response.body, "Unexpected response data for #{http_range}" ) assert_equal( should_be.length.to_s, response['Content-Length'], "Incorrect Content-Length for #{http_range}" ) assert_equal( expected_range, response['Content-Range'], "Incorrect Content-Range for #{http_range}" ) end it 'handles valid byte ranges correctly' do # Use the biggest file in this dir so we can test ranges > 8k bytes. (StaticFile sends in 8k chunks.) path = File.dirname(__FILE__) + '/helpers_test.rb' # currently 16k bytes file = File.read(path) length = file.length assert length > 9000, "The test file #{path} is too short (#{length} bytes) to run these tests" [0..0, 42..88, 1234..1234, 100..9000, 0..(length-1), (length-1)..(length-1)].each do |range| assert_valid_range("bytes=#{range.begin}-#{range.end}", range, path, file) end [0, 100, length-100, length-1].each do |start| assert_valid_range("bytes=#{start}-", (start..length-1), path, file) end [1, 100, length-100, length-1, length].each do |range_length| assert_valid_range("bytes=-#{range_length}", (length-range_length..length-1), path, file) end # Some valid ranges that exceed the length of the file: assert_valid_range("bytes=100-999999", (100..length-1), path, file) assert_valid_range("bytes=100-#{length}", (100..length-1), path, file) assert_valid_range("bytes=-#{length}", (0..length-1), path, file) assert_valid_range("bytes=-#{length+1}", (0..length-1), path, file) assert_valid_range("bytes=-999999", (0..length-1), path, file) end it 'correctly ignores syntactically invalid range requests' do # ...and also ignores multi-range requests, which aren't supported yet ["bytes=45-40", "bytes=IV-LXVI", "octets=10-20", "bytes=-", "bytes=1-2,3-4"].each do |http_range| request = Rack::MockRequest.new(@app) response = request.get("/#{File.basename(__FILE__)}", 'HTTP_RANGE' => http_range) assert_equal( 200, response.status, "Invalid range '#{http_range}' should be ignored" ) assert_nil( response['Content-Range'], "Invalid range '#{http_range}' should be ignored" ) end end it 'returns error 416 for unsatisfiable range requests' do # An unsatisfiable request is one that specifies a start that's at or past the end of the file. length = File.read(__FILE__).length ["bytes=888888-", "bytes=888888-999999", "bytes=#{length}-#{length}"].each do |http_range| request = Rack::MockRequest.new(@app) response = request.get("/#{File.basename(__FILE__)}", 'HTTP_RANGE' => http_range) assert_equal( 416, response.status, "Unsatisfiable range '#{http_range}' should return 416" ) assert_equal( "bytes */#{length}", response['Content-Range'], "416 response should include actual length" ) end end it 'does not include static cache control headers by default' do env = Rack::MockRequest.env_for("/#{File.basename(__FILE__)}") _, headers, _ = @app.call(env) assert !headers.has_key?('Cache-Control') end it 'sets cache control headers on static files if set' do @app.set :static_cache_control, :public env = Rack::MockRequest.env_for("/#{File.basename(__FILE__)}") _, headers, _ = @app.call(env) assert headers.has_key?('Cache-Control') assert_equal headers['Cache-Control'], 'public' @app.set( :static_cache_control, [:public, :must_revalidate, {:max_age => 300}] ) env = Rack::MockRequest.env_for("/#{File.basename(__FILE__)}") _, headers, _ = @app.call(env) assert headers.has_key?('Cache-Control') assert_equal( headers['Cache-Control'], 'public, must-revalidate, max-age=300' ) end it 'renders static assets with custom status via options' do mock_app do set :static, true set :public_folder, File.dirname(__FILE__) post '/*' do static!(:status => params[:status]) end end post "/#{File.basename(__FILE__)}?status=422" assert_equal response.status, 422 assert_equal File.read(__FILE__), body assert_equal File.size(__FILE__).to_s, response['Content-Length'] assert response.headers.include?('Last-Modified') end it 'serves files with a + sign in the path' do mock_app do set :static, true set :public_folder, File.join(File.dirname(__FILE__), 'public') end get "/hello+world.txt" real_path = File.join(File.dirname(__FILE__), 'public', 'hello+world.txt') assert ok? assert_equal File.read(real_path), body assert_equal File.size(real_path).to_s, response['Content-Length'] assert response.headers.include?('Last-Modified') end it 'serves files with a URL encoded + sign (%2B) in the path' do mock_app do set :static, true set :public_folder, File.join(File.dirname(__FILE__), 'public') end get "/hello%2bworld.txt" real_path = File.join(File.dirname(__FILE__), 'public', 'hello+world.txt') assert ok? assert_equal File.read(real_path), body assert_equal File.size(real_path).to_s, response['Content-Length'] assert response.headers.include?('Last-Modified') end end sinatra-2.0.8.1/test/streaming_test.rb000066400000000000000000000070211360317524000176560ustar00rootroot00000000000000require File.expand_path('../helper', __FILE__) class StreamingTest < Minitest::Test Stream = Sinatra::Helpers::Stream it 'returns the concatenated body' do mock_app do get('/') do stream do |out| out << "Hello" << " " out << "World!" end end end get('/') assert_body "Hello World!" end it 'always yields strings' do stream = Stream.new { |out| out << :foo } stream.each { |str| assert_equal 'foo', str } end it 'postpones body generation' do step = 0 stream = Stream.new do |out| 10.times do out << step step += 1 end end stream.each do |s| assert_equal s, step.to_s step += 1 end end it 'calls the callback after it is done' do step = 0 final = 0 stream = Stream.new { |_| 10.times { step += 1 }} stream.callback { final = step } stream.each {|_|} assert_equal 10, final end it 'does not trigger the callback if close is set to :keep_open' do step = 0 final = 0 stream = Stream.new(Stream, :keep_open) { |_| 10.times { step += 1 } } stream.callback { final = step } stream.each {|_|} assert_equal 0, final end it 'allows adding more than one callback' do a = b = false stream = Stream.new { } stream.callback { a = true } stream.callback { b = true } stream.each {|_| } assert a, 'should trigger first callback' assert b, 'should trigger second callback' end class MockScheduler def initialize(*) @schedule, @defer = [], [] end def schedule(&block) @schedule << block end def defer(&block) @defer << block end def schedule!(*) @schedule.pop.call until @schedule.empty? end def defer!(*) @defer.pop.call until @defer.empty? end end it 'allows dropping in another scheduler' do scheduler = MockScheduler.new processing = sending = done = false stream = Stream.new(scheduler) do |out| processing = true out << :foo end stream.each { sending = true} stream.callback { done = true } scheduler.schedule! assert !processing assert !sending assert !done scheduler.defer! assert processing assert !sending assert !done scheduler.schedule! assert sending assert done end it 'schedules exceptions to be raised on the main thread/event loop/...' do scheduler = MockScheduler.new Stream.new(scheduler) { fail 'should be caught' }.each { } scheduler.defer! assert_raises(RuntimeError) { scheduler.schedule! } end it 'does not trigger an infinite loop if you call close in a callback' do stream = Stream.new { |out| out.callback { out.close }} stream.each { |_| } end it 'gives access to route specific params' do mock_app do get('/:name') do stream { |o| o << params[:name] } end end get '/foo' assert_body 'foo' end it 'sets up async.close if available' do ran = false mock_app do get('/') do close = Object.new def close.callback; yield end def close.errback; end env['async.close'] = close stream(:keep_open) do |out| out.callback { ran = true } end end end get '/' assert ran end it 'has a public interface to inspect its open/closed state' do stream = Stream.new(Stream) { |out| out << :foo } assert !stream.closed? stream.close assert stream.closed? end end sinatra-2.0.8.1/test/stylus_test.rb000066400000000000000000000044021360317524000172300ustar00rootroot00000000000000require File.expand_path('../helper', __FILE__) begin require 'stylus' require 'stylus/tilt' begin Stylus.compile '1' rescue RuntimeError raise LoadError, 'unable to find Stylus compiler' end class StylusTest < Minitest::Test def stylus_app(options = {}, &block) mock_app do set :views, File.dirname(__FILE__) + '/views' set(options) get('/', &block) end get '/' end it 'renders inline Stylus strings' do stylus_app { stylus "a\n margin auto\n" } assert ok? assert body.include?("a {\n margin: auto;\n}\n") end it 'defaults content type to css' do stylus_app { stylus :hello } assert ok? assert_equal "text/css;charset=utf-8", response['Content-Type'] end it 'defaults allows setting content type per route' do stylus_app do content_type :html stylus :hello end assert ok? assert_equal "text/html;charset=utf-8", response['Content-Type'] end it 'defaults allows setting content type globally' do stylus_app(:styl => { :content_type => 'html' }) do stylus :hello end assert ok? assert_equal "text/html;charset=utf-8", response['Content-Type'] end it 'renders .styl files in views path' do stylus_app { stylus :hello } assert ok? assert_include body, "a {\n margin: auto;\n}\n" end it 'ignores the layout option' do stylus_app { stylus :hello, :layout => :layout2 } assert ok? assert_include body, "a {\n margin: auto;\n}\n" end it "raises error if template not found" do mock_app { get('/') { stylus :no_such_template } } assert_raises(Errno::ENOENT) { get('/') } end it "passes stylus options to the stylus engine" do stylus_app { stylus :hello, :no_wrap => true } assert ok? assert_body "a {\n margin: auto;\n}\n" end it "passes default stylus options to the stylus engine" do mock_app do set :stylus, :no_wrap => true # default stylus style is :nested get('/') { stylus :hello } end get '/' assert ok? assert_body "a {\n margin: auto;\n}\n" end end rescue LoadError warn "#{$!}: skipping stylus tests" end sinatra-2.0.8.1/test/templates_test.rb000066400000000000000000000256411360317524000176730ustar00rootroot00000000000000# encoding: UTF-8 require File.expand_path('../helper', __FILE__) File.delete(File.dirname(__FILE__) + '/views/layout.test') rescue nil class TestTemplate < Tilt::Template def prepare end def evaluate(scope, locals={}, &block) inner = block ? block.call : '' data + inner end Tilt.register 'test', self end class TemplatesTest < Minitest::Test def render_app(base=Sinatra::Base, options = {}, &block) base, options = Sinatra::Base, base if base.is_a? Hash mock_app(base) do set :views, File.dirname(__FILE__) + '/views' set options get('/', &block) template(:layout3) { "Layout 3!\n" } end get '/' end def with_default_layout layout = File.dirname(__FILE__) + '/views/layout.test' File.open(layout, 'wb') { |io| io.write "Layout!\n" } yield ensure File.unlink(layout) rescue nil end it 'falls back to engine layout' do mock_app do template(:layout3) { 'Layout 3!<%= yield %>' } set :erb, :layout => :layout3 get('/') do erb('Hello World!', { :layout => true }) end end get '/' assert ok? assert_equal "Layout 3!Hello World!", body end it 'falls back to default layout if engine layout is true' do mock_app do template(:layout) { 'Layout!!! <%= yield %>' } set :erb, :layout => true get('/') do erb('Hello World!', { :layout => true }) end end get '/' assert ok? assert_equal "Layout!!! Hello World!", body end it 'renders no layout if layout if falsy' do mock_app do template(:layout) { 'Layout!!! <%= yield %>' } set :erb, :layout => true get('/') do erb('Hello World!', { :layout => nil }) end end get '/' assert ok? assert_equal "Hello World!", body end it 'allows overriding false default layout with explicit true' do mock_app do template(:layout) { 'Layout!!! <%= yield %>' } set :erb, :layout => false get('/') do erb('Hello World!', { :layout => true }) end end get '/' assert ok? assert_equal "Layout!!! Hello World!", body end it 'renders String templates directly' do render_app { render(:test, 'Hello World') } assert ok? assert_equal 'Hello World', body end it 'allows to specify path/line when rendering with String' do path = 'example.txt' line = 228 begin render_app { render :erb, '<%= doesnotexist %>', {:path => path, :line => line} } rescue NameError => e assert_match(/#{path}:#{line}/, e.backtrace.first) end end it 'allows to specify path/line when rendering with Proc' do path = 'example.txt' line = 900 begin render_app { render :erb, Proc.new { '<%= doesnotexist %>' }, {:path => path, :line => line} } rescue NameError => e assert_match(/#{path}:#{line}/, e.backtrace.first) end end it 'renders Proc templates using the call result' do render_app { render(:test, Proc.new {'Hello World'}) } assert ok? assert_equal 'Hello World', body end it 'looks up Symbol templates in views directory' do render_app { render(:test, :hello) } assert ok? assert_equal "Hello World!\n", body end it 'uses the default layout template if not explicitly overridden' do with_default_layout do render_app { render(:test, :hello) } assert ok? assert_equal "Layout!\nHello World!\n", body end end it 'uses the default layout template if not really overridden' do with_default_layout do render_app { render(:test, :hello, :layout => true) } assert ok? assert_equal "Layout!\nHello World!\n", body end end it 'uses the layout template specified' do render_app { render(:test, :hello, :layout => :layout2) } assert ok? assert_equal "Layout 2!\nHello World!\n", body end it 'uses layout templates defined with the #template method' do render_app { render(:test, :hello, :layout => :layout3) } assert ok? assert_equal "Layout 3!\nHello World!\n", body end it 'avoids wrapping layouts around nested templates' do render_app { render(:str, :nested, :layout => :layout2) } assert ok? assert_equal( "

String Layout!

\n

Hello From String

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

String Layout!

\n

String Layout!

\n

Hello From String

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

String Layout!

\n

Hello From String

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

content

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

content

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

String Layout!

\nGimme an A!\n", body end it 'loads templates defined in subclasses' do base = Class.new(Sinatra::Base) base.template(:foo) { 'bar' } render_app(base) { render(:test, :foo) } assert ok? assert_equal 'bar', body end it 'allows setting default content type' do render_app(:str => { :default_content_type => :txt }) { render :str, 'foo' } assert_equal 'text/plain;charset=utf-8', response['Content-Type'] end it 'allows setting default content type per template engine' do render_app(:str => { :content_type => :txt }) { render :str, 'foo' } assert_equal 'text/plain;charset=utf-8', response['Content-Type'] end it 'setting default content type does not affect other template engines' do render_app(:str => { :content_type => :txt }) { render :test, 'foo' } assert_equal 'text/html;charset=utf-8', response['Content-Type'] end it 'setting default content type per template engine does not override content_type' do render_app :str => { :content_type => :txt } do content_type :html render :str, 'foo' end assert_equal 'text/html;charset=utf-8', response['Content-Type'] end it 'uses templates in superclasses before subclasses' do base = Class.new(Sinatra::Base) base.template(:foo) { 'template in superclass' } assert_equal 'template in superclass', base.templates[:foo].first.call mock_app(base) do set :views, File.dirname(__FILE__) + '/views' template(:foo) { 'template in subclass' } get('/') { render :test, :foo } end assert_equal 'template in subclass', @app.templates[:foo].first.call get '/' assert ok? assert_equal 'template in subclass', body end it "is possible to use a different engine for the layout than for the template itself explicitly" do render_app do settings.template(:layout) { 'Hello <%= yield %>!' } render :str, "<%= 'World' %>", :layout_engine => :erb end assert_equal "Hello <%= 'World' %>!", body end it "is possible to use a different engine for the layout than for the template itself globally" do render_app :str => { :layout_engine => :erb } do settings.template(:layout) { 'Hello <%= yield %>!' } render :str, "<%= 'World' %>" end assert_equal "Hello <%= 'World' %>!", body end it "does not leak the content type to the template" do render_app :str => { :layout_engine => :erb } do settings.template(:layout) { 'Hello <%= yield %>!' } render :str, "<%= 'World' %>", :content_type => :txt end assert_equal "text/html;charset=utf-8", headers['Content-Type'] end it "is possible to register another template" do Tilt.register "html.erb", Tilt[:erb] render_app { render :erb, :calc } assert_equal '2', body end it "passes scope to the template" do mock_app do template(:scoped) { 'Hello <%= foo %>' } get('/') do some_scope = Object.new def some_scope.foo() 'World!' end erb :scoped, :scope => some_scope end end get '/' assert ok? assert_equal 'Hello World!', body end it "is possible to use custom logic for finding template files" do mock_app do set :views, ["a", "b"].map { |d| File.dirname(__FILE__) + '/views/' + d } def find_template(views, name, engine, &block) Array(views).each { |v| super(v, name, engine, &block) } end get('/:name') { render(:str, params[:name].to_sym) } end get '/in_a' assert_body 'Gimme an A!' get '/in_b' assert_body 'Gimme a B!' end end # __END__ : this is not the real end of the script. __END__ @@ foo this is foo @@ bar There's a space after 'bar'! @@ foo bar this is not foo @@ umlaut Den som tror at hemma det är där man bor har aldrig vart hos mig. @@ layout X = yield X sinatra-2.0.8.1/test/textile_test.rb000066400000000000000000000027761360317524000173570ustar00rootroot00000000000000require File.expand_path('../helper', __FILE__) begin require 'redcloth' class TextileTest < Minitest::Test def textile_app(&block) mock_app do set :views, File.dirname(__FILE__) + '/views' get('/', &block) end get '/' end it 'renders inline textile strings' do textile_app { textile('h1. Hiya') } assert ok? assert_equal "

Hiya

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

Hello From Textile

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

SPARTA

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

Hello World

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

hi

', body end end rescue LoadError warn "#{$!}: skipping textile tests" end sinatra-2.0.8.1/test/views/000077500000000000000000000000001360317524000154365ustar00rootroot00000000000000sinatra-2.0.8.1/test/views/a/000077500000000000000000000000001360317524000156565ustar00rootroot00000000000000sinatra-2.0.8.1/test/views/a/in_a.str000066400000000000000000000000141360317524000173110ustar00rootroot00000000000000Gimme an A! sinatra-2.0.8.1/test/views/ascii.erb000066400000000000000000000000551360317524000172200ustar00rootroot00000000000000This file has no unicode in it! <%= value %> sinatra-2.0.8.1/test/views/b/000077500000000000000000000000001360317524000156575ustar00rootroot00000000000000sinatra-2.0.8.1/test/views/b/in_b.str000066400000000000000000000000131360317524000173120ustar00rootroot00000000000000Gimme a B! sinatra-2.0.8.1/test/views/calc.html.erb000066400000000000000000000000141360317524000177700ustar00rootroot00000000000000<%= 1 + 1 %>sinatra-2.0.8.1/test/views/error.builder000066400000000000000000000000431360317524000201340ustar00rootroot00000000000000xml.error do raise "goodbye" end sinatra-2.0.8.1/test/views/error.erb000066400000000000000000000002031360317524000172540ustar00rootroot00000000000000Hello <%= 'World' %> <% raise 'Goodbye' unless defined?(french) && french %> <% raise 'Au revoir' if defined?(french) && french %> sinatra-2.0.8.1/test/views/error.haml000066400000000000000000000001721360317524000174320ustar00rootroot00000000000000%h1 Hello From Haml = raise 'goodbye' unless defined?(french) && french = raise 'au revoir' if defined?(french) && french sinatra-2.0.8.1/test/views/error.sass000066400000000000000000000000261360317524000174600ustar00rootroot00000000000000#sass +argle-bargle sinatra-2.0.8.1/test/views/explicitly_nested.str000066400000000000000000000000761360317524000217230ustar00rootroot00000000000000#{render :str, :hello, :layout => :layout2}sinatra-2.0.8.1/test/views/foo/000077500000000000000000000000001360317524000162215ustar00rootroot00000000000000sinatra-2.0.8.1/test/views/foo/hello.test000066400000000000000000000000351360317524000202230ustar00rootroot00000000000000from another views directory sinatra-2.0.8.1/test/views/hello.asciidoc000066400000000000000000000000271360317524000202400ustar00rootroot00000000000000== Hello from AsciiDoc sinatra-2.0.8.1/test/views/hello.builder000066400000000000000000000000471360317524000201120ustar00rootroot00000000000000xml.exclaim "You're my boy, #{@name}!" sinatra-2.0.8.1/test/views/hello.coffee000066400000000000000000000000151360317524000177060ustar00rootroot00000000000000alert "Aye!" sinatra-2.0.8.1/test/views/hello.creole000066400000000000000000000000241360317524000177300ustar00rootroot00000000000000= Hello From Creole sinatra-2.0.8.1/test/views/hello.erb000066400000000000000000000000251360317524000172300ustar00rootroot00000000000000Hello <%= 'World' %> sinatra-2.0.8.1/test/views/hello.haml000066400000000000000000000000241360317524000174000ustar00rootroot00000000000000%h1 Hello From Haml sinatra-2.0.8.1/test/views/hello.less000066400000000000000000000001011360317524000174210ustar00rootroot00000000000000@white_colour: #fff; #main { background-color: @white_colour; }sinatra-2.0.8.1/test/views/hello.liquid000066400000000000000000000000331360317524000177460ustar00rootroot00000000000000

Hello From Liquid

sinatra-2.0.8.1/test/views/hello.mab000066400000000000000000000000301360317524000172130ustar00rootroot00000000000000h1 "Hello From Markaby" sinatra-2.0.8.1/test/views/hello.md000066400000000000000000000000251360317524000170600ustar00rootroot00000000000000# Hello From Markdownsinatra-2.0.8.1/test/views/hello.mediawiki000066400000000000000000000000311360317524000204200ustar00rootroot00000000000000''Hello from MediaWiki'' sinatra-2.0.8.1/test/views/hello.nokogiri000066400000000000000000000000471360317524000203050ustar00rootroot00000000000000xml.exclaim "You're my boy, #{@name}!" sinatra-2.0.8.1/test/views/hello.rabl000066400000000000000000000000341360317524000174000ustar00rootroot00000000000000object @foo attributes :bar sinatra-2.0.8.1/test/views/hello.radius000066400000000000000000000000331360317524000177460ustar00rootroot00000000000000

Hello From Radius

sinatra-2.0.8.1/test/views/hello.rdoc000066400000000000000000000000221360317524000174040ustar00rootroot00000000000000= Hello From RDoc sinatra-2.0.8.1/test/views/hello.sass000066400000000000000000000000401360317524000174260ustar00rootroot00000000000000#sass background-color: white sinatra-2.0.8.1/test/views/hello.scss000066400000000000000000000000431360317524000174330ustar00rootroot00000000000000#scss { background-color: white }sinatra-2.0.8.1/test/views/hello.slim000066400000000000000000000000231360317524000174220ustar00rootroot00000000000000h1 Hello From Slim sinatra-2.0.8.1/test/views/hello.str000066400000000000000000000000321360317524000172660ustar00rootroot00000000000000

Hello From String

sinatra-2.0.8.1/test/views/hello.styl000066400000000000000000000000201360317524000174460ustar00rootroot00000000000000a margin auto sinatra-2.0.8.1/test/views/hello.test000066400000000000000000000000151360317524000174360ustar00rootroot00000000000000Hello World! sinatra-2.0.8.1/test/views/hello.textile000066400000000000000000000000271360317524000201400ustar00rootroot00000000000000h1. Hello From Textile sinatra-2.0.8.1/test/views/hello.wlang000066400000000000000000000000221360317524000175650ustar00rootroot00000000000000Hello from wlang! sinatra-2.0.8.1/test/views/hello.yajl000066400000000000000000000000331360317524000174160ustar00rootroot00000000000000json = { :yajl => "hello" }sinatra-2.0.8.1/test/views/layout2.builder000066400000000000000000000000411360317524000204000ustar00rootroot00000000000000xml.layout do xml << yield end sinatra-2.0.8.1/test/views/layout2.erb000066400000000000000000000000311360317524000175210ustar00rootroot00000000000000ERB Layout! <%= yield %> sinatra-2.0.8.1/test/views/layout2.haml000066400000000000000000000000331360317524000176740ustar00rootroot00000000000000%h1 HAML Layout! %p= yield sinatra-2.0.8.1/test/views/layout2.liquid000066400000000000000000000000531360317524000202440ustar00rootroot00000000000000

Liquid Layout!

{{ yield }}

sinatra-2.0.8.1/test/views/layout2.mab000066400000000000000000000000411360317524000175110ustar00rootroot00000000000000h1 "Markaby Layout!" p { yield } sinatra-2.0.8.1/test/views/layout2.nokogiri000066400000000000000000000000411360317524000205730ustar00rootroot00000000000000xml.layout do xml << yield end sinatra-2.0.8.1/test/views/layout2.rabl000066400000000000000000000000501360317524000176720ustar00rootroot00000000000000node(:qux) do ::JSON.parse(yield) end sinatra-2.0.8.1/test/views/layout2.radius000066400000000000000000000000531360317524000202440ustar00rootroot00000000000000

Radius Layout!

sinatra-2.0.8.1/test/views/layout2.slim000066400000000000000000000000351360317524000177210ustar00rootroot00000000000000h1 Slim Layout! p == yield sinatra-2.0.8.1/test/views/layout2.str000066400000000000000000000000401360317524000175610ustar00rootroot00000000000000

String Layout!

#{yield}sinatra-2.0.8.1/test/views/layout2.test000066400000000000000000000000121360317524000177270ustar00rootroot00000000000000Layout 2! sinatra-2.0.8.1/test/views/layout2.wlang000066400000000000000000000000271360317524000200660ustar00rootroot00000000000000WLang Layout! +{yield} sinatra-2.0.8.1/test/views/nested.str000066400000000000000000000000511360317524000174460ustar00rootroot00000000000000#{render :str, :hello}sinatra-2.0.8.1/test/views/utf8.erb000066400000000000000000000000771360317524000170220ustar00rootroot00000000000000

<%= value %>

Ingen vill veta var du köpt din tröja. sinatra-2.0.8.1/test/wlang_test.rb000066400000000000000000000036561360317524000170070ustar00rootroot00000000000000require File.expand_path('../helper', __FILE__) begin require 'wlang' class WLangTest < Minitest::Test def engine Tilt::WLangTemplate end def wlang_app(&block) mock_app { set :views, File.dirname(__FILE__) + '/views' get '/', &block } get '/' end it 'uses the correct engine' do assert_equal engine, Tilt[:wlang] end it 'renders .wlang files in views path' do wlang_app { wlang :hello } assert ok? assert_equal "Hello from wlang!\n", body end it 'renders in the app instance scope' do mock_app do helpers do def who; "world"; end end get('/') { wlang 'Hello +{who}!' } end get '/' assert ok? assert_equal 'Hello world!', body end it 'takes a :locals option' do wlang_app do locals = {:foo => 'Bar'} wlang 'Hello ${foo}!', :locals => locals end assert ok? assert_equal 'Hello Bar!', body end it "renders with inline layouts" do mock_app do layout { 'THIS. IS. +{yield.upcase}!' } get('/') { wlang 'Sparta' } end get '/' assert ok? assert_equal 'THIS. IS. SPARTA!', body end it "renders with file layouts" do wlang_app { wlang 'Hello World', :layout => :layout2 } assert ok? assert_body "WLang Layout!\nHello World" end it "can rendered truly nested layouts by accepting a layout and a block with the contents" do mock_app do template(:main_outer_layout) { "

Title

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

Subtitle

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

Contents.

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

Title

\n

Subtitle

\n

Contents.

\n" end end rescue LoadError warn "#{$!}: skipping wlang tests" end sinatra-2.0.8.1/test/yajl_test.rb000066400000000000000000000034721360317524000166320ustar00rootroot00000000000000require File.expand_path('../helper', __FILE__) begin require 'yajl' class YajlTest < Minitest::Test def yajl_app(&block) mock_app do set :views, File.dirname(__FILE__) + '/views' get('/', &block) end get '/' end it 'renders inline Yajl strings' do yajl_app { yajl('json = { :foo => "bar" }') } assert ok? assert_body '{"foo":"bar"}' end it 'renders .yajl files in views path' do yajl_app { yajl(:hello) } assert ok? assert_body '{"yajl":"hello"}' end it 'raises error if template not found' do mock_app { get('/') { yajl(:no_such_template) } } assert_raises(Errno::ENOENT) { get('/') } end it 'accepts a :locals option' do yajl_app do locals = { :object => { :foo => 'bar' } } yajl 'json = object', :locals => locals end assert ok? assert_body '{"foo":"bar"}' end it 'accepts a :scope option' do yajl_app do scope = { :object => { :foo => 'bar' } } yajl 'json = self[:object]', :scope => scope end assert ok? assert_body '{"foo":"bar"}' end it 'decorates the json with a callback' do yajl_app do yajl( 'json = { :foo => "bar" }', { :callback => 'baz' } ) end assert ok? assert_body 'baz({"foo":"bar"});' end it 'decorates the json with a variable' do yajl_app do yajl( 'json = { :foo => "bar" }', { :variable => 'qux' } ) end assert ok? assert_body 'var qux = {"foo":"bar"};' end it 'decorates the json with a callback and a variable' do yajl_app do yajl( 'json = { :foo => "bar" }', { :callback => 'baz', :variable => 'qux' } ) end assert ok? assert_body 'var qux = {"foo":"bar"}; baz(qux);' end end rescue LoadError warn "#{$!}: skipping yajl tests" end