sinatra-1.4.8/0000755000004100000410000000000013044044066013216 5ustar www-datawww-datasinatra-1.4.8/Rakefile0000644000004100000410000001377413044044066014677 0ustar www-datawww-datarequire 'rake/clean' require 'rake/testtask' require 'fileutils' require 'date' # CI Reporter is only needed for the CI begin require 'ci/reporter/rake/test_unit' rescue LoadError end task :default => :test task :spec => :test CLEAN.include "**/*.rbc" def source_version @source_version ||= begin load './lib/sinatra/version.rb' Sinatra::VERSION end end def prev_feature source_version.gsub(/^(\d\.)(\d+)\..*$/) { $1 + ($2.to_i - 1).to_s } end def prev_version return prev_feature + '.0' if source_version.end_with? '.0' source_version.gsub(/\d+$/) { |s| s.to_i - 1 } end # SPECS =============================================================== task :test do ENV['LANG'] = 'C' ENV.delete 'LC_CTYPE' end Rake::TestTask.new(:test) do |t| t.test_files = FileList['test/*_test.rb'] t.ruby_opts = ['-rubygems'] if defined? Gem t.ruby_opts << '-I.' 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 = ["-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" sh "rcov -Ilib test/*_test.rb" end end # Website ============================================================= desc 'Generate RDoc under doc/api' task 'doc' => ['doc:api'] task('doc:api') { sh "yardoc -o doc/api" } CLEAN.include 'doc/api' # README =============================================================== task :add_template, [:name] do |t, args| Dir.glob('README.*') do |file| code = File.read(file) if code =~ /^===.*#{args.name.capitalize}/ puts "Already covered in #{file}" else template = code[/===[^\n]*Liquid.*index\.liquid<\/tt>[^\n]*/m] if !template puts "Liquid not found in #{file}" else puts "Adding section to #{file}" template = template.gsub(/Liquid/, args.name.capitalize).gsub(/liquid/, args.name.downcase) code.gsub! /^(\s*===.*CoffeeScript)/, "\n" << template << "\n\\1" File.open(file, "w") { |f| f << code } end end end end # Thanks in announcement =============================================== team = ["Ryan Tomayko", "Blake Mizerany", "Simon Rozet", "Konstantin Haase"] desc "list of contributors" task :thanks, [:release,:backports] do |t, a| a.with_defaults :release => "#{prev_version}..HEAD", :backports => "#{prev_feature}.0..#{prev_feature}.x" included = `git log --format=format:"%aN\t%s" #{a.release}`.lines.map { |l| l.force_encoding('binary') } excluded = `git log --format=format:"%aN\t%s" #{a.backports}`.lines.map { |l| l.force_encoding('binary') } commits = (included - excluded).group_by { |c| c[/^[^\t]+/] } authors = commits.keys.sort_by { |n| - commits[n].size } - team puts authors[0..-2].join(', ') << " and " << authors.last, "(based on commits included in #{a.release}, but not in #{a.backports})" end desc "list of authors" task :authors, [:commit_range, :format, :sep] do |t, a| a.with_defaults :format => "%s (%d)", :sep => ", ", :commit_range => '--all' authors = Hash.new(0) blake = "Blake Mizerany" overall = 0 mapping = { "blake.mizerany@gmail.com" => blake, "bmizerany" => blake, "a_user@mac.com" => blake, "ichverstehe" => "Harry Vangberg", "Wu Jiang (nouse)" => "Wu Jiang" } `git shortlog -s #{a.commit_range}`.lines.map do |line| line = line.force_encoding 'binary' if line.respond_to? :force_encoding num, name = line.split("\t", 2).map(&:strip) authors[mapping[name] || name] += num.to_i overall += num.to_i end puts "#{overall} commits by #{authors.count} authors:" puts authors.sort_by { |n,c| -c }.map { |e| a.format % e }.join(a.sep) end desc "generates TOC" task :toc, [:readme] do |t, a| a.with_defaults :readme => 'README.md' def self.link(title) title.downcase.gsub(/(?!-)\W /, '-').gsub(' ', '-').gsub(/(?!-)\W/, '') end puts "* [Sinatra](#sinatra)" title = Regexp.new('(?<=\* )(.*)') # so Ruby 1.8 doesn't complain File.binread(a.readme).scan(/^##.*/) do |line| puts line.gsub(/#(?=#)/, ' ').gsub('#', '*').gsub(title) { "[#{$1}](##{link($1)})" } end end # PACKAGING ============================================================ if defined?(Gem) # Load the gemspec using the same limitations as github def spec require 'rubygems' unless defined? Gem::Specification @spec ||= eval(File.read('sinatra.gemspec')) end def package(ext='') "pkg/sinatra-#{spec.version}" + ext end desc 'Build packages' task :package => %w[.gem .tar.gz].map {|e| package(e)} desc 'Build and install as local gem' task :install => package('.gem') do sh "gem install #{package('.gem')}" end directory 'pkg/' CLOBBER.include('pkg') file package('.gem') => %w[pkg/ sinatra.gemspec] + spec.files do |f| sh "gem build sinatra.gemspec" mv File.basename(f.name), f.name end file package('.tar.gz') => %w[pkg/] + spec.files do |f| sh <<-SH git archive \ --prefix=sinatra-#{source_version}/ \ --format=tar \ HEAD | gzip > #{f.name} SH end task 'release' => ['test', package('.gem')] do if File.binread("CHANGELOG.md") =~ /= \d\.\d\.\d . not yet released$/i fail 'please update the changelog first' unless %x{git symbolic-ref HEAD} == "refs/heads/prerelease\n" end sh <<-SH gem install #{package('.gem')} --local && gem push #{package('.gem')} && git commit --allow-empty -a -m '#{source_version} release' && git tag -s v#{source_version} -m '#{source_version} release' && git tag -s #{source_version} -m '#{source_version} release' && git push && (git push sinatra || true) && git push --tags && (git push sinatra --tags || true) SH end end sinatra-1.4.8/Gemfile0000644000004100000410000000336313044044066014516 0ustar www-datawww-data# Why use bundler? # Well, not all development dependencies install on all rubies. Moreover, `gem # install sinatra --development` doesn't work, as it will also try to install # development dependencies of our dependencies, and those are not conflict free. # So, here we are, `bundle install`. # # If you have issues with a gem: `bundle install --without-coffee-script`. RUBY_ENGINE = 'ruby' unless defined? RUBY_ENGINE source 'https://rubygems.org' unless ENV['QUICK'] gemspec gem 'rake', '~> 10.0' gem 'rack-test', '>= 0.6.2' gem "minitest", "~> 5.0" if RUBY_ENGINE == 'jruby' gem 'nokogiri', '!= 1.5.0' gem 'jruby-openssl' gem 'trinidad' end if RUBY_VERSION < '1.9.3' gem 'activesupport', '~> 3.2' gem 'i18n', '~> 0.6.0' else gem 'activesupport', '~> 4.2' end if RUBY_ENGINE == "ruby" if RUBY_VERSION > '1.9.2' gem 'less', '~> 2.0' gem 'therubyracer' gem 'redcarpet' gem 'wlang', '>= 2.0.1' gem 'bluecloth' gem 'rdiscount' gem 'RedCloth' gem 'puma' gem 'net-http-server' gem 'yajl-ruby' 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', '~> 3.0' gem 'stylus' gem 'rabl' gem 'builder' gem 'erubis' gem 'haml', '>= 3.0' gem 'sass' if RUBY_VERSION < '2.1.0' gem 'nokogiri', '~> 1.6.8' else gem 'nokogiri', '~> 1.7.0' end else gem 'nokogiri', '~> 1.5.11' end end if RUBY_ENGINE == "rbx" gem 'json' gem 'rubysl' gem 'rubysl-test-unit' end platforms :ruby_18, :jruby do gem 'json', '~> 1.8' unless RUBY_VERSION > '1.9' # is there a jruby but 1.8 only selector? end sinatra-1.4.8/examples/0000755000004100000410000000000013044044066015034 5ustar www-datawww-datasinatra-1.4.8/examples/stream.ru0000644000004100000410000000106413044044066016700 0ustar www-datawww-data# this example does *not* work properly with WEBrick # # run *one* of these: # # rackup -s mongrel stream.ru # gem install mongrel # thin -R stream.ru start # gem install thin # unicorn stream.ru # gem install unicorn # puma stream.ru # gem install puma require 'sinatra/base' class Stream < Sinatra::Base get '/' do content_type :txt stream do |out| out << "It's gonna be legen -\n" sleep 0.5 out << " (wait for it) \n" sleep 1 out << "- dary!\n" end end end run Stream sinatra-1.4.8/examples/simple.rb0000755000004100000410000000013313044044066016652 0ustar www-datawww-data#!/usr/bin/env ruby -I ../lib -I lib require 'sinatra' get('/') { 'this is a simple app' } sinatra-1.4.8/examples/chat.rb0000755000004100000410000000245713044044066016313 0ustar www-datawww-data#!/usr/bin/env ruby -I ../lib -I lib # coding: utf-8 require 'sinatra' set :server, 'thin' connections = [] get '/' do halt erb(:login) unless params[:user] erb :chat, :locals => { :user => params[:user].gsub(/\W/, '') } end get '/stream', :provides => 'text/event-stream' do stream :keep_open do |out| connections << out out.callback { connections.delete(out) } end end post '/' do connections.each { |out| out << "data: #{params[:msg]}\n\n" } 204 # response without entity body end __END__ @@ layout Super Simple Chat with Sinatra <%= yield %> @@ login
@@ chat

sinatra-1.4.8/README.ko.md0000644000004100000410000024750413044044066015121 0ustar www-datawww-data# 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 /\A\/hello\/([\w]+)\z/ 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 상태 코드가 되는 어떠한 객체라도 반환할 수 있습니다. * 세 요소를 가진 배열: `[상태 (Fixnum), 헤더 (Hash), 응답 본문 (#each에 반응)]` * 두 요소를 가진 배열: `[상태 (Fixnum), 응답 본문 (#each에 반응)]` * `#each`에 반응하고 주어진 블록으로 문자열만을 전달하는 객체 * 상태 코드를 의미하는 Fixnum 이것을 이용한 예를 들자면, 스트리밍(streaming) 예제를 쉽게 구현할 수 있습니다. ```ruby class Stream def each 100.times { |i| yield "#{i}\n" } end end get('/') { Stream.new } ``` `stream` 헬퍼 메서드(아래 참조)를 사용하면 이런 번거로움을 줄이고 스트리밍 로직을 라우터 속에 포함 시킬 수도 있습니다. ### 커스텀 라우터 매처(Custom Route Matchers) 위에서 보듯, Sinatra에는 문자열 패턴 및 정규표현식을 이용한 라우터 매칭 지원이 내장되어 있습니다. 하지만, 그게 끝은 아닙니다. 여러분 만의 매처(matcher)도 쉽게 정의할 수 있습니다. ```ruby class AllButPattern Match = Struct.new(:captures) def initialize(except) @except = except @captures = Match.new([]) end def match(str) @captures unless @except === str end end def all_but(pattern) AllButPattern.new(pattern) end get all_but("/index") do # ... end ``` 사실 위의 예제는 조금 과하게 작성된 면이 있습니다. 간단하게 표현할 수도 있어요. ```ruby get // do pass if request.path_info == "/index" # ... end ``` 또는 거꾸로 탐색(negative look ahead)할 수도 있습니다. ```ruby get %r{^(?!/index$)} do # ... end ``` ## 정적 파일(Static Files) 정적 파일들은 `./public` 디렉터리에서 제공됩니다. 위치를 다른 곳으로 변경하려면 `:public_folder` 옵션을 지정하면 됩니다. ```ruby set :public_folder, File.dirname(__FILE__) + '/static' ``` public 디렉터리명은 URL에 포함되지 않는다는 점에 주의하세요. `./public/css/style.css` 파일은 아마 `http://example.com/css/style.css` 로 접근할 수 있을 것입니다. `Cache-Control` 헤더 정보를 추가하려면 `:static_cache_control` 설정(아래 참조)을 사용하면 됩니다. ## 뷰 / 템플릿(Views / Templates) 템플릿 언어들은 각각의 렌더링 메서드를 통해 표출됩니다. 이들 메서드는 문자열을 반환할 뿐입니다. ```ruby get '/' do erb :index end ``` 이 구문은 `views/index.erb`를 렌더합니다. 템플릿 이름 대신 템플릿의 내용을 직접 넘길 수도 있습니다. ```ruby get '/' do code = "<%= Time.now %>" erb code end ``` 템플릿은 두 번째 인자로 옵션값의 해시를 받습니다. ```ruby get '/' do erb :index, :layout => :post end ``` 이 구문은 `views/post.erb` 속에 내장된 `views/index.erb`를 렌더합니다. (`views/layout.erb`파일이 존재할 경우 기본값은 `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 ``` 환경(RACK_ENV 환경 변수)이 `:production`일 때만 실행되게 하려면 이렇게 하면 됩니다. ```ruby configure :production do ... end ``` 환경이 `:production` 또는 `:test`일 때 실행되게 하려면 이렇게 하면 됩니다. ```ruby configure :production, :test do ... end ``` 이 옵션들은 `settings`를 통해 접근 가능합니다. ```ruby configure do set :foo, 'bar' end get '/' do settings.foo? # => true settings.foo # => 'bar' ... end ``` ### 공격 방어 설정하기(Configuring attack protection) Sinatra는 [Rack::Protection](https://github.com/sinatra/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['RACK_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"` 가 있습니다. 환경은 `RACK_ENV` 환경 변수를 통해서도 설정됩니다. 기본값은 `"development"`입니다. `"development"` 모드에서는 모든 템플릿들은 요청 간에 리로드됩니다. 또, `"development"` 모드에서는 특별한 `not_found` 와 `error` 핸들러가 브라우저에서 스택 트레이스를 볼 수 있게합니다. `"production"`과 `"test"`에서는 기본적으로 템플릿은 캐시됩니다. 다른 환경으로 실행시키려면 `RACK_ENV` 환경 변수를 사용하세요. ```shell RACK_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_rack_env 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-1.4.8/AUTHORS.md0000644000004100000410000000676213044044066014700 0ustar www-datawww-dataSinatra was designed and developed by Blake Mizerany in California. ### Current Team * **Konstantin Haase** (maintainer) * **Zachary Scott** * **Kashyap Kondamudi** * **Ashley Williams** * **Trevor Bramble** ### 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-1.4.8/README.ja.md0000644000004100000410000026726713044044066015112 0ustar www-datawww-data# 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 ``` ルーティングは定義された順番にマッチします。 リクエストに最初にマッチしたルーティングが呼び出されます。 ルーティングのパターンは名前付きパラメータを含むことができ、 `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 /\A\/hello\/([\w]+)\z/ 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つの要素を含む配列: `[ステータス(Fixnum), ヘッダ(Hash), レスポンスボディ(#eachに応答する)]` * 2つの要素を含む配列: `[ステータス(Fixnum), レスポンスボディ(#eachに応答する)]` * `#each`に応答するオブジェクト。通常はそのまま何も返さないが、 与えられたブロックに文字列を渡す。 * ステータスコードを表現する整数(Fixnum) これにより、例えばストリーミングを簡単に実装することができます。 ```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'`のようにします。 レンダリングメソッドは文字列が渡されると、それをそのまま文字列として出力するので、シンボルを使ってください。 ### リテラルテンプレート(Literal Templates) ```ruby get '/' do haml '%div.title Hello World' end ``` これはそのテンプレート文字列をレンダリングします。 ### 利用可能なテンプレート言語 いくつかの言語には複数の実装があります。使用する(そしてスレッドセーフにする)実装を指定するには、それを最初にrequireしてください。 ```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`を除く)を呼び出すことができないため、ほぼ全ての場合に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 を参照してください。 ## フィルタ(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' ``` ### 停止(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; http://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 ``` ### ロギング(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 '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 ``` ### キャッシュ制御(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 ``` 環境設定(`RACK_ENV`環境変数)が`:production`に設定されている時だけ実行する方法: ```ruby configure :production do ... end ``` 環境設定が`:production`か`:test`に設定されている時だけ実行する方法: ```ruby configure :production, :test do ... end ``` 設定したオプションには`settings`からアクセスできます: ```ruby configure do set :foo, 'bar' end get '/' do settings.foo? # => true settings.foo # => 'bar' ... end ``` ### 攻撃防御に対する設定 Sinatraは、[Rack::Protection](https://github.com/sinatra/rack-protection#readme)を使って、アプリケーションを多発する日和見的攻撃から守っています。この挙動は簡単に無効化できます(これはアプリケーションを大量の脆弱性攻撃に晒すことになります)。 ```ruby disable :protection ``` 単一の防御層を外すためには、`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['RACK_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"`があります。環境は、`RACK_ENV`環境変数を通して設定できます。デフォルト値は、`"development"`です。`"development"`環境において、すべてのテンプレートは、各リクエスト間で再ロードされ、そして、特別の`not_found`および`error`ハンドラがブラウザにスタックトレースを表示します。`"production"`および`"test"`環境においては、テンプレートはデフォルトでキャッシュされます。 異なる環境を走らせるには、`RACK_ENV`環境変数を設定します。 ```shell RACK_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`ハンドラはルーティングブロックまたはフィルタ内で例外が発生したときはいつでも発動します。例外オブジェクトは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](http://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_rack_env 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][so-answer]での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 ``` [so-answer]: http://stackoverflow.com/questions/6278817/is-sinatra-multi-threaded/6282999#6282999) ## 必要環境 次の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](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および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](http://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) * [メーリングリスト](http://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-1.4.8/README.es.md0000644000004100000410000021317313044044066015112 0ustar www-datawww-data# Sinatra *Atención: Este documento es una traducción de la versión en inglés y puede estar desactualizado.* Sinatra es un [DSL](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 ``` Instalar la gema y correr la aplicación con: ```shell gem install sinatra ruby miapp.rb ``` Ver en [http://localhost:4567](http://localhost:4567). Se recomienda ejecutar `gem install thin`, porque Sinatra lo utilizará si está disponible. ## 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 escogida. 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/al/archivo", "xml"] end ``` Rutas con Expresiones Regulares: ```ruby get /\A\/hola\/([\w]+)\z/ do "Hola, #{params['captures'].first}!" end ``` O con un parámetro de bloque: ```ruby get %r{/hola/([\w]+)} do |c| "Hola, #{c}!" end ``` Los patrones de ruta pueden contener parámetros opcionales: ```ruby get '/posts/:formato?' do # coincide con "GET /posts/" y además admite cualquier extensión, por # ejemplo, "GET /posts/json", "GET /posts/xml", etc. end ``` A propósito, a menos que desactives la protección para el ataque *path traversal* (ver más abajo), el path de la petición puede ser modificado antes de que se compare con los de tus rutas. ## Condiciones Las rutas pueden incluir una variedad de condiciones de selección, como por ejemplo el user agent: ```ruby get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do "Estás usando la versión de Songbird #{params['agent'][0]}" end get '/foo' do # Coincide con 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 ``` 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 ``` Si su condición acepta más de un argumento, puede pasarle un arreglo. Al definir la condición, se puede utilizar el operador splat en la lista de parámetros: ```ruby set(:autorizar) do |*roles| # <- mirá el splat condition do unless sesion_iniciada? && roles.any? {|rol| usuario_actual.tiene_rol? rol } redirect "/iniciar_sesion/", 303 end end end get "/mi/cuenta/", :autorizar => [:usuario, :administrador] do "Detalles de mi cuenta" end get "/solo/administradores/", :autorizar => :administrador do "Únicamente para administradores!" end ``` ### Valores de Retorno El valor de retorno de un bloque de ruta que determina al menos el cuerpo de la respuesta que se le pasa al cliente HTTP o al siguiente middleware en la pila de Rack. Lo más común es que sea un string, como en los ejemplos anteriores. Sin embargo, otros valores también son aceptados. Puede devolver cualquier objeto que sea una respuesta Rack válida, un objeto que represente el cuerpo de una respuesta Rack o un código de estado HTTP: * Un arreglo con tres elementos: `[estado (Fixnum), cabeceras (Hash), cuerpo de la respuesta (responde a #each)]` * Un arreglo con dos elementos: `[estado (Fixnum), cuerpo de la respuesta (responde a #each)]` * Un objeto que responde a `#each` y que le pasa únicamente strings al bloque dado * Un Fixnum representando el código de estado De esa manera, podemos fácilmente implementar un ejemplo de streaming: ```ruby class Stream def each 100.times { |i| yield "#{i}\n" } end end get('/') { Stream.new } ``` ### Comparadores de Rutas Personalizados Como se mostró anteriormente, Sinatra permite utilizar strings y expresiones regulares para definir las rutas. Sin embargo, la cosa no termina ahí. Podés definir tus propios comparadores muy fácilmente: ```ruby class PatronCualquieraMenos Match = Struct.new(:captures) def initialize(excepto) @excepto = excepto @capturas = Match.new([]) end def match(str) @capturas unless @excepto === str end end def cualquiera_menos(patron) PatronCualquieraMenos.new(patron) end get cualquiera_menos("/index") do # ... end ``` 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 lookahead 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__) + '/estaticos' ``` 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 la sección de configuración para más detalles). ### Vistas / Plantillas Cada lenguaje de plantilla se expone a través de un método de renderizado que lleva su nombre. Estos métodos simplemente devuelven un string: ```ruby get '/' do erb :index end ``` Renderiza `views/index.erb`. En lugar del nombre de la plantilla podés proporcionar directamente el contenido de la misma: ```ruby get '/' do codigo = "<%= Time.now %>" erb codigo end ``` Los métodos de renderizado, aceptan además un segundo argumento, el hash de opciones: ```ruby get '/' do erb :index, :layout => :post end ``` Renderiza `views/index.erb` 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 símbolo que especifique qué plantilla usar. Ejemplo: erb :index, :layout => !request.xhr?
content_type
Content-Type que produce la plantilla. El valor por defecto depende de cada lenguaje de plantillas.
scope
Ámbito en el que se renderiza la plantilla. Por defecto utiliza la instancia de la aplicación. Tené en cuenta que si cambiás esta opción las variables de instancia y los helpers van a dejar de estar disponibles.
layout_engine
Motor de renderizado de plantillas que usa para el layout. Resulta conveniente para lenguajes que no soportan layouts. Por defecto toma el valor del motor usado para renderizar la plantilla. Ejemplo: set :rdoc, :layout_engine => :erb
Se asume que las plantillas están ubicadas directamente bajo el directorio ./views. Para usar un directorio de vistas diferente: set :views, settings.root + '/plantillas'
Es importante acordarse que siempre tenés que referenciar a las plantillas con símbolos, incluso cuando se encuentran en un subdirectorio (en este caso tenés que usar: `:'subdir/plantilla'` o `'subdir/plantilla'.to_sym`). Tenés que usar un símbolo porque los métodos de renderización van a renderizar directamente cualquier string que se les pase como argumento.
### Lenguajes de Plantillas Disponibles Algunos lenguajes tienen varias implementaciones. Para especificar que implementación usar (y para ser thread-safe), deberías requerirla antes de usarla: ```ruby require 'rdiscount' # o require 'bluecloth' get('/') { markdown :index } ``` ### Plantillas Haml
Dependencias haml
Expresiones de Archivo .haml
Ejemplo haml :index, :format => :html5
### Plantillas Erb
Dependencias erubis o erb (incluida en Ruby)
Extensiones de Archivo .erb, .rhtml o .erubis (solamente con Erubis)
Ejemplo erb :index
### Plantillas Builder
Dependencias builder
Extensiones de Archivo .builder
Ejemplo builder { |xml| xml.em "hola" }
Además, acepta un bloque con la definición de la plantilla (ver ejemplo). ### Plantillas Nokogiri
Dependencias nokogiri
Extensiones de Archivo .nokogiri
Ejemplo nokogiri { |xml| xml.em "hola" }
Además, acepta un bloque con la definición de la plantilla (ver ejemplo). ### Plantillas Sass
Dependencias sass
Extensiones de Archivo .sass
Ejemplo sass :stylesheet, :style => :expanded
### Plantillas SCSS
Dependencias sass
Extensiones de Archivo .scss
Ejemplo scss :stylesheet, :style => :expanded
### Plantillas Less
Dependencias less
Extensiones de Archivo .less
Ejemplo less :stylesheet
### Plantillas Liquid
Dependencias liquid
Extensiones de Archivo .liquid
Ejemplo liquid :index, :locals => { :clave => 'valor' }
Como no 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 podés 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 podés llamar al método `textile` desde otras plantillas: ```ruby %h1 Hola Desde Haml! %p= textile(:saludos) ``` Como no podés utilizar Ruby desde Textile, no podés usar layouts escritos en Textile. De todos modos, es posible usar un motor de renderizado para el layout distinto al de la plantilla pasando la opción `:layout_engine`. ### Plantillas RDoc
Dependencias RDoc
Extensiones de Archivo .rdoc
Ejemplo rdoc :README, :layout_engine => :erb
No es posible llamar métodos desde rdoc, ni pasarle locales. Por lo tanto, generalmente vas a usarlo en combinación con otro motor de renderizado: ```ruby erb :resumen, :locals => { :texto => rdoc(:introduccion) } ``` Tené en cuenta que también podés llamar al método `rdoc` desde otras plantillas: ```ruby %h1 Hola Desde Haml! %p= rdoc(:saludos) ``` Como no podés utilizar Ruby desde RDoc, no podés usar layouts escritos en RDoc. De todos modos, es posible usar un motor de renderizado para el layout distinto al de la plantilla pasando la opción `:layout_engine`. ### Plantillas Radius
Dependencias Radius
Extensiones de Archivo .radius
Ejemplo radius :index, :locals => { :clave => 'valor' }
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!" }
Además, acepta un bloque con la definición de la plantilla (ver ejemplo). ### Plantillas RABL
Dependencias Rabl
Extensiones de Archivo .rabl
Ejemplo rabl :index
### Plantillas Slim
Dependencias Slim Lang
Extensiones de Archivo .slim
Ejemplo slim :index
### Plantillas Creole
Dependencias Creole
Extensiones de Archivo .creole
Ejemplo creole :wiki, :layout_engine => :erb
No es posible llamar métodos desde creole, ni pasarle locales. Por lo tanto, generalmente 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 podés utilizar Ruby desde Creole, no podés 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`. ### Plantillas CoffeeScript
Dependencias CoffeeScript y un mecanismo para ejecutar javascript
Extensiones de Archivo .coffee
Ejemplo coffee :index
### Plantillas Stylus
Dependencias Stylus y un mecanismo para ejecutar javascript
Extensiones de Archivo .styl
Ejemplo stylus :index
### Plantillas Yajl
Dependencias yajl-ruby
Extensiones de Archivo .yajl
Ejemplo yajl :index, :locals => { :key => 'qux' }, :callback => 'present', :variable => 'resource'
El contenido de la plantilla se evalúa como código Ruby, y la variable `json` es convertida a JSON mediante `#to_json`. ```ruby json = { :foo => 'bar' } json[:baz] = key ``` Las opciones `:callback` y `:variable` se pueden utilizar para decorar el objeto renderizado: ```ruby var resource = {"foo":"bar","baz":"qux"}; present(resource); ``` ### Plantillas WLang
Dependencias wlang
Extensiones de Archivo .wlang
Ejemplo wlang :index, :locals => { :clave => 'valor' }
Como no vas a poder llamar a métodos de Ruby (excepto por `yield`) desde una plantilla WLang, casi siempre vas a querer pasarle locales. ### Plantillas Embebidas ```ruby get '/' do haml '%div.titulo Hola Mundo' end ``` Renderiza el template embebido en el string. ### Accediendo a Variables en Plantillas Las plantillas son evaluadas dentro del mismo contexto que los manejadores de ruta. Las variables de instancia asignadas en los manejadores de ruta son accesibles directamente por las plantillas: ```ruby get '/:id' do @foo = Foo.find(params['id']) haml '%h1= @foo.nombre' end ``` O es posible especificar un Hash de variables locales explícitamente: ```ruby get '/:id' do foo = Foo.find(params['id']) haml '%h1= bar.nombre', :locals => { :bar => foo } end ``` Esto es usado típicamente cuando se renderizan plantillas como parciales desde adentro de otras plantillas. ### Plantillas Inline Las plantillas pueden ser definidas al final del archivo fuente: ```ruby require 'rubygems' require 'sinatra' get '/' do haml :index end __END__ @@ layout %html = yield @@ index %div.titulo Hola mundo!!!!! ``` NOTA: únicamente las plantillas inline definidas en el archivo fuente que requiere Sinatra son cargadas automáticamente. Llamá `enable :inline_templates` explícitamente si tenés plantillas inline en otros archivos fuente. ### Plantillas Nombradas Las plantillas también pueden ser definidas usando el método top-level `template`: ```ruby template :layout do "%html\n =yield\n" end template :index do '%div.titulo Hola Mundo!' end get '/' do haml :index end ``` Si existe una plantilla con el nombre "layout", va a ser usada cada vez que una plantilla es renderizada. Podés desactivar los layouts individualmente pasando `:layout => false` o globalmente con `set :haml, :layout => false`: ```ruby get '/' do haml :index, :layout => !request.xhr? end ``` ### Asociando Extensiones de Archivo Para asociar una extensión de archivo con un motor de renderizado, usá `Tilt.register`. Por ejemplo, si querés usar la extensión `tt` para las plantillas Textile, podés hacer lo siguiente: ```ruby Tilt.register :tt, Tilt[:textile] ``` ### Agregando Tu Propio Motor de Renderizado Primero, registrá tu motor con Tilt, y después, creá tu método de renderizado: ```ruby Tilt.register :mipg, MiMotorParaPlantillaGenial helpers do def mypg(*args) render(:mypg, *args) end end get '/' do mypg :index end ``` Renderiza `./views/index.mypg`. Mirá https://github.com/rtomayko/tilt para aprender más de Tilt. ## Filtros Los filtros `before` son evaluados antes de cada petición dentro del mismo contexto que las rutas. Pueden modificar la petición y la respuesta. Las variables de instancia asignadas en los filtros son accesibles por las rutas y las plantillas: ```ruby before do @nota = 'Hey!' request.path_info = '/foo/bar/baz' end get '/foo/*' do @nota #=> 'Hey!' params['splat'] #=> 'bar/baz' end ``` Los filtros `after` son evaluados después de cada petición dentro del mismo contexto y también pueden modificar la petición y la respuesta. Las variables de instancia asignadas en los filtros `before` y en las rutas son accesibles por los filtros `after`: ```ruby after do puts response.status end ``` Nota: A menos que uses el método `body` en lugar de simplemente devolver un string desde una ruta, el cuerpo de la respuesta no va a estar disponible en un filtro after, debido a que todavía no se ha generado. Los filtros aceptan un patrón opcional, que cuando está presente causa que los mismos sean evaluados únicamente si el path de la petición coincide con ese patrón: ```ruby before '/protegido/*' do autenticar! end after '/crear/:slug' do |slug| session[:ultimo_slug] = slug end ``` Al igual que las rutas, los filtros también pueden aceptar condiciones: ```ruby before :agent => /Songbird/ do # ... end after '/blog/*', :host_name => 'ejemplo.com' do # ... end ``` ## Ayudantes Usá el método top-level *helpers* para definir métodos ayudantes que pueden ser utilizados dentro de los manejadores de rutas y las plantillas: ```ruby helpers do def bar(nombre) "#{nombre}bar" end end get '/:nombre' do bar(params['nombre']) end ``` Por cuestiones organizativas, puede resultar conveniente organizar los métodos ayudantes en distintos módulos: ```ruby module FooUtils def foo(nombre) "#{nombre}foo" end end module BarUtils def bar(nombre) "#{nombre}bar" end end helpers FooUtils, BarUtils ``` El efecto de utilizar *helpers* de esta manera es el mismo que resulta de incluir los módulos en la clase de la aplicación. ### Usando Sesiones Una sesión es usada para mantener el estado a través de distintas peticiones. Cuando están activadas, proporciona un hash de sesión para cada sesión de usuario: ```ruby enable :sessions get '/' do "valor = " << session[:valor].inspect end get '/:valor' do session[:valor] = params['valor'] end ``` Tené en cuenta que `enable :sessions` guarda todos los datos en una cookie, lo cual no es siempre deseable (guardar muchos datos va a incrementar el tráfico, por citar un ejemplo). Podés usar cualquier middleware Rack para manejar sesiones, de la misma manera que usarías cualquier otro middleware, pero con la salvedad de que *no* tenés que llamar a `enable :sessions`: ```ruby use Rack::Session::Pool, :expire_after => 2592000 get '/' do "valor = " << session[:valor].inspect end get '/:valor' do session[:valor] = params['valor'] end ``` Para incrementar la seguridad, los datos de la sesión almacenados en la cookie son firmados con un secreto de sesión. Este secreto, es generado aleatoriamente por Sinatra. De cualquier manera, hay que tener en cuenta que cada vez que inicies la aplicación se va a generar uno nuevo. Así, si querés que todas las instancias de tu aplicación compartan un único secreto, tenés que definirlo vos: ```ruby set :session_secret, 'super secreto' ``` Si necesitás una configuración más específica, `sessions` acepta un Hash con opciones: ```ruby set :sessions, :domain => 'foo.com' ``` ### Interrupción Para detener inmediatamente una petición dentro de un filtro o una ruta usá: ```ruby halt ``` También podés especificar el estado: ```ruby halt 410 ``` O el cuerpo: ```ruby halt 'esto va a ser el cuerpo' ``` O los dos: ```ruby halt 401, 'salí de acá!' ``` Con cabeceras: ```ruby halt 402, { 'Content-Type' => 'text/plain' }, 'venganza' ``` Obviamente, es posible utilizar `halt` con una plantilla: ```ruby halt erb(:error) ``` ### Paso Una ruta puede pasarle el procesamiento a la siguiente ruta que coincida con la petición usando `pass`: ```ruby get '/adivina/:quien' do pass unless params['quien'] == 'Franco' 'Adivinaste!' end get '/adivina/*' do 'Erraste!' end ``` Se sale inmediatamente del bloque de la ruta y se le pasa el control a la siguiente ruta que coincida. Si no coincide ninguna ruta, se devuelve 404. ### Ejecutando Otra Ruta Cuando querés obtener el resultado de la llamada a una ruta, `pass` no te va a servir. Para lograr esto, podés usar `call`: ```ruby get '/foo' do status, headers, body = call env.merge("PATH_INFO" => '/bar') [status, headers, body.map(&:upcase)] end get '/bar' do "bar" end ``` Notá que en el ejemplo anterior, es conveniente mover `"bar"` a un helper, y llamarlo desde `/foo` y `/bar`. Así, vas a simplificar las pruebas y a mejorar el rendimiento. Si querés que la petición se envíe a la misma instancia de la aplicación en lugar de otra, usá `call!` en lugar de `call`. En la especificación de Rack podés encontrar más información sobre `call`. ### Asignando el Código de Estado, los Encabezados y el Cuerpo de una Respuesta Es posible, y se recomienda, asignar el código de estado y el cuerpo de una respuesta con el valor de retorno de una ruta. De cualquier manera, en varios escenarios, puede que sea conveniente asignar el cuerpo en un punto arbitrario del flujo de ejecución con el método `body`. A partir de ahí, podés usar ese mismo método para acceder al cuerpo de la respuesta: ```ruby get '/foo' do body "bar" end after do puts body end ``` También es posible pasarle un bloque a `body`, que será ejecutado por el Rack handler (podés usar esto para implementar streaming, mirá "Valores de retorno"). De manera similar, también podés asignar el código de estado y encabezados: ```ruby get '/foo' do status 418 headers \ "Allow" => "BREW, POST, GET, PROPFIND, WHEN", "Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt" body "I'm a tea pot!" end ``` También, al igual que `body`, tanto `status` como `headers` pueden utilizarse para obtener sus valores cuando no se les pasa argumentos. ### Streaming De Respuestas A veces vas a querer empezar a enviar la respuesta a pesar de que todavía no terminaste de generar su cuerpo. También es posible que, en algunos casos, quieras seguir enviando información hasta que el cliente cierre la conexión. Cuando esto ocurra, el 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 ``` Podés implementar APIs de streaming, [Server-Sent Events](https://w3c.github.io/eventsource/) 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. Queda en vos cerrarlo en el punto de ejecución que quieras. Nuevamente, hay que tener en cuenta que este comportamiento es posible solo en servidores que soporten eventos, como Thin o Rainbows. El resto de los servidores van a cerrar el stream de todos modos: ```ruby set :server, :thin conexiones = [] get '/' do # mantenemos abierto el stream stream(:keep_open) { |salida| conexiones << salida } end post '/' do # escribimos a todos los streams abiertos conexiones.each { |salida| salida << params['mensaje'] << "\n" } "mensaje enviado" end ``` ### Log (Registro) En el ámbito de la petición, el helper `logger` (registrador) expone una instancia de `Logger`: ```ruby get '/' do logger.info "cargando datos" # ... end ``` Este logger tiene en cuenta la configuración de logueo de tu Rack handler. Si el logueo está desactivado, este método va a devolver un objeto que se comporta como un logger pero que en realidad no hace nada. Así, no vas a tener que preocuparte por esta situación. Tené en cuenta que el logueo está habilitado por defecto únicamente para `Sinatra::Application`. Si heredaste de `Sinatra::Base`, probablemente quieras habilitarlo manualmente: ```ruby class MiApp < Sinatra::Base configure :production, :development do enable :logging end end ``` Para evitar que se inicialice cualquier middleware de logging, configurá `logging` a `nil`. Tené en cuenta que, cuando hagas esto, `logger` va a devolver `nil`. Un caso común es cuando querés usar tu propio logger. Sinatra va a usar lo que encuentre en `env['rack.logger']`. ### Tipos Mime Cuando usás `send_file` o archivos estáticos tal vez tengas tipos mime que Sinatra no entiende. Usá `mime_type` para registrarlos a través de la extensión de archivo: ```ruby configure do mime_type :foo, 'text/foo' end ``` También lo podés usar con el ayudante `content_type`: ```ruby get '/' do content_type :foo "foo foo foo" end ``` ### Generando URLs Para generar URLs deberías usar el método `url`. Por ejemplo, en Haml: ```ruby %a{:href => url('/foo')} foo ``` Tiene en cuenta proxies inversos y encaminadores de Rack, si están presentes. Este método también puede invocarse mediante su alias `to` (mirá un ejemplo a continuación). ### Redirección del Navegador Podés redireccionar al navegador con el método `redirect`: ```ruby get '/foo' do redirect to('/bar') end ``` Cualquier parámetro adicional se utiliza de la misma manera que los argumentos pasados a `halt`: ```ruby redirect to('/bar'), 303 redirect 'http://www.google.com/', 'te confundiste de lugar, compañero' ``` También podés redireccionar fácilmente de vuelta hacia la página desde donde vino el usuario con `redirect back`: ```ruby get '/foo' do "hacer algo" end get '/bar' do hacer_algo redirect back end ``` Para pasar argumentos con una redirección, podés agregarlos a la cadena de búsqueda: ```ruby redirect to('/bar?suma=42') ``` O usar una sesión: ```ruby enable :sessions get '/foo' do session[:secreto] = 'foo' redirect to('/bar') end get '/bar' do session[:secreto] end ``` ### Cache Control Asignar tus encabezados correctamente es el cimiento para realizar un cacheo HTTP correcto. Podés asignar el encabezado Cache-Control fácilmente: ```ruby get '/' do cache_control :public "cachealo!" end ``` Pro tip: configurar el cacheo en un filtro `before`: ```ruby before do cache_control :public, :must_revalidate, :max_age => 60 end ``` Si estás usando el helper `expires` para definir el encabezado correspondiente, `Cache-Control` se va a definir automáticamente: ```ruby before do expires 500, :public, :must_revalidate end ``` Para usar cachés adecuadamente, deberías considerar usar `etag` o `last_modified`. Es recomendable que llames a estos 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. Podés cambiar este comportamiento con la opción `:new_resource`: ```ruby get '/crear' do etag '', :new_resource => true Articulo.create erb :nuevo_articulo end ``` Si querés seguir usando una weak ETag, indicalo con la opción `:kind`: ```ruby etag '', :new_resource => true, :kind => :weak ``` ### Enviando Archivos Para enviar archivos, podés usar el método `send_file`: ```ruby get '/' do send_file 'foo.png' end ``` Además acepta un par de opciones: ```ruby send_file 'foo.png', :type => :jpg ``` Estas opciones son: [filename] nombre del archivo devuelto, por defecto es el nombre real del archivo. [last_modified] valor para el encabezado Last-Modified, por defecto toma el mtime del archivo. [type] el content type que se va a utilizar, si no está presente se intenta adivinar a partir de la extensión del archivo. [disposition] se utiliza para el encabezado Content-Disposition, y puede tomar alguno de los siguientes valores: `nil` (por defecto), `:attachment` e `:inline` [length] encabezado Content-Length, por defecto toma el tamaño del archivo. [status] código de estado devuelto. Resulta útil al enviar un archivo estático como una página de error. Si el Rack handler lo soporta, se intentará no transmitir directamente desde el proceso de Ruby. Si usás este método, Sinatra se va a encargar automáticamente de las peticiones de rango. ### Accediendo al objeto de la petición El objeto de la petición entrante puede ser accedido desde el nivel de la petición (filtros, rutas y manejadores de errores) a través del método `request`: ```ruby # app corriendo en http://ejemplo.com/ejemplo get '/foo' do t = %w[text/css text/html application/javascript] request.accept # ['text/html', '*/*'] request.accept? 'text/xml' # true request.preferred_type(t) # 'text/html' request.body # cuerpo de la petición enviado por el cliente (ver más abajo) request.scheme # "http" request.script_name # "/ejemplo" request.path_info # "/foo" request.port # 80 request.request_method # "GET" request.query_string # "" request.content_length # longitud de request.body request.media_type # tipo de medio de request.body request.host # "ejemplo.com" request.get? # true (hay métodos análogos para los otros verbos) request.form_data? # false request["UNA_CABECERA"] # valor de la cabecera UNA_CABECERA request.referrer # la referencia del cliente o '/' request.user_agent # user agent (usado por la condición :agent) request.cookies # hash de las cookies del 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) requuest.env # hash de entorno directamente entregado por Rack end ``` Algunas opciones, como `script_name` o `path_info` pueden también ser escritas: ```ruby before { request.path_info = "/" } get "/" do "todas las peticiones llegan acá" end ``` El objeto `request.body` es una instancia de IO o StringIO: ```ruby post "/api" do request.body.rewind # en caso de que alguien ya lo haya leído datos = JSON.parse request.body.read "Hola #{datos['nombre']}!" end ``` ### Archivos Adjuntos Podés usar el helper `attachment` para indicarle al navegador que almacene la respuesta en el disco en lugar de mostrarla en pantalla: ```ruby get '/' do attachment "guardalo!" end ``` También podés pasarle un nombre de archivo: ```ruby get '/' do attachment "info.txt" "guardalo!" end ``` ### Fecha y Hora Sinatra pone a tu disposición el helper `time_for`, que genera un objeto `Time` a partir del valor que recibe como argumento. Este valor puede ser un `String`, pero también es capaz de convertir objetos `DateTime`, `Date` y de otras clases similares: ```ruby get '/' do pass if Time.now > time_for('Dec 23, 2012') "todavía hay tiempo" end ``` Este método es usado internamente por métodos como `expires` y `last_modified`, entre otros. Por lo tanto, es posible extender el comportamiento de estos métodos sobreescribiendo `time_for` en tu aplicación: ```ruby helpers do def time_for(value) case value when :ayer then Time.now - 24*60*60 when :mañana then Time.now + 24*60*60 else super end end end get '/' do last_modified :ayer expires :mañana "hola" end ``` ### Buscando los Archivos de las Plantillas El helper `find_template` se utiliza para encontrar los archivos de las plantillas que se van a renderizar: ```ruby find_template settings.views, 'foo', Tilt[:haml] do |archivo| puts "podría ser #{archivo}" end ``` Si bien esto no es muy útil, lo interesante es que podés sobreescribir este método, y así enganchar tu propio mecanismo de búsqueda. Por ejemplo, para poder utilizar más de un directorio de vistas: ```ruby set :views, ['vistas', 'plantillas'] helpers do def find_template(views, name, engine, &block) Array(views).each { |v| super(v, name, engine, &block) } end end ``` Otro ejemplo consiste en usar directorios diferentes para los distintos motores de renderizado: ```ruby set :views, :sass => 'vistas/sass', :haml => 'plantillas', :defecto => 'vistas' helpers do def find_template(views, name, engine, &block) _, folder = views.detect { |k,v| engine == Tilt[k] } folder ||= views[:defecto] super(folder, name, engine, &block) end end ``` ¡Es muy fácil convertir estos ejemplos en una extensión y compartirla! Notá que `find_template` no verifica si un archivo existe realmente, sino que llama al bloque que recibe para cada path posible. Esto no representa un problema de rendimiento debido a que `render` va a usar `break` ni bien encuentre un archivo que exista. Además, las ubicaciones de las plantillas (y su contenido) se cachean cuando no estás en el modo de desarrollo. Es bueno tener en cuenta lo 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 podés tener configuraciones dinámicas usando bloques set(:css_dir) { File.join(views, 'css') } end ``` Ejecutar únicamente cuando el entorno (la variable de entorno RACK_ENV) es `:production`: ```ruby configure :production do ... end ``` Ejecutar cuando el entorno es `:production` o `:test`: ```ruby configure :production, :test do ... end ``` Podés acceder a estas opciones utilizando el método `settings`: ```ruby configure do set :foo, 'bar' end get '/' do settings.foo? # => true settings.foo # => 'bar' ... end ``` ### Configurando la Protección de Ataques Sinatra usa [Rack::Protection](https://github.com/sinatra/rack-protection#readme) para defender a tu aplicación de los ataques más comunes. Si por algún motivo, querés desactivar esta funcionalidad, podés hacerlo como se indica a continuación (ten en cuenta que tu aplicación va a quedar expuesta a un montón de vulnerabilidades bien conocidas): ```ruby disable :protection ``` También es posible desactivar una única capa de defensa: ```ruby set :protection, :except => :path_traversal ``` O varias: ```ruby set :protection, :except => [:path_traversal, :session_hijacking] ``` ### Configuraciones Disponibles
absolute_redirects
Si está deshabilitada, Sinatra va a permitir redirecciones relativas, sin embargo, como consecuencia de esto, va a dejar de cumplir con el RFC 2616 (HTTP 1.1), que solamente permite redirecciones absolutas. Activalo si tu apliación está corriendo atrás de un proxy reverso que no se ha configurado adecuadamente. Notá que el helper url va a seguir produciendo URLs absolutas, a menos que le pasés false como segundo parámetro. Deshabilitada por defecto.
add_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).
default_encoding
Encoding utilizado cuando el mismo se desconoce (por defecto "utf-8").
dump_errors
Mostrar errores en el log.
environment
Entorno actual, por defecto toma el valor de ENV['RACK_ENV'], o "development" si no está disponible.
logging
Define si se utiliza el logger.
lock
Coloca un lock alrededor de cada petición, procesando solamente una por proceso. Habilitá esta opción si tu aplicación no es thread-safe. Se encuentra deshabilitada por defecto.
method_override
Utiliza el parámetro _method para permtir formularios put/delete en navegadores que no los soportan.
port
Puerto en el que escuchará el servidor integrado.
prefixed_redirects
Define si inserta request.script_name en las redirecciones cuando no se proporciona un path absoluto. De esta manera, cuando está habilitada, redirect '/foo' se comporta de la misma manera que redirect to('/foo'). Se encuentra deshabilitada por defecto.
protection
Define si deben activarse las protecciones para los ataques web más comunes. Para más detalles mirá la sección sobre la configuración de protección de ataques más arriba.
public_dir
Alias para public_folder, que se encuentra a continuación.
public_folder
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.
reload_templates
Define si se recargan las plantillas entre peticiones. Se encuentra activado en el entorno 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.
sessions
Habilita el soporte de sesiones basadas en cookies a través de Rack::Session::Cookie. Ver la sección 'Usando Sesiones' para más información.
show_exceptions
Muestra un stack trace en el navegador cuando ocurre una excepción. Se encuentra activada por defecto cuando el valor de environment es "development". En caso contrario estará desactivada.
static
Define si Sinatra debe encargarse de servir archivos estáticos. Deshabilitala cuando uses un servidor capaz de hacerlo por sí solo, porque mejorará el rendimiento. Se encuentra habilitada por defecto en el estilo clásico y desactivado en el el modular.
static_cache_control
Cuando Sinatra está sirviendo archivos estáticos, y 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].
views
Path del directorio de las vistas. Si no está presente, se infiere del valor de la opción app_file.
## Entornos Existen tres entornos (`environments`) predefinidos: `development`, `production` y `test`. El entorno por defecto es `development` y tiene algunas particularidades: * Se recargan las plantillas entre una petición y la siguiente, a diferencia de `production` y `test`, donde se cachean. * Se instalan manejadores de errores `not_found` y `error` especiales que muestran un stack trace en el navegador cuando son disparados. Para utilizar alguno de los otros entornos puede asignarse el valor correspondiente a la variable de entorno `RACK_ENV`, o bien utilizar la opción `-e` al ejecutar la aplicación: ```shell ruby mi_app.rb -e ``` Los métodos `development?`, `test?` y `production?` te permiten conocer el entorno actual. ## Manejo de Errores Los manejadores de errores se ejecutan dentro del mismo contexto que las rutas y los filtros `before`, lo que significa que podés usar, por ejemplo, `haml`, `erb`, `halt`, etc. ### No encontrado (Not Found) Cuando se eleva una excepción `Sinatra::NotFound`, o el código de estado de la respuesta es 404, el manejador `not_found` es invocado: ```ruby not_found do 'No existo' end ``` ### Error El manejador `error` es invocado cada vez que una excepción es elevada desde un bloque de ruta o un filtro. El objeto de la excepción se puede obtener de la variable Rack `sinatra.error`: ```ruby error do 'Disculpá, ocurrió un error horrible - ' + env['sinatra.error'].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 ``` Obtenés esto: Lo que pasó fue... algo malo También, podés instalar un manejador de errores para un código de estado: ```ruby error 403 do 'Acceso prohibido' end get '/secreto' do 403 end ``` O un rango: ```ruby error 400..510 do 'Boom' end ``` Sinatra instala manejadores `not_found` y `error` especiales cuando se ejecuta dentro del entorno de desarrollo "development". ## Rack Middleware Sinatra corre sobre [Rack](http://rack.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. Podés encontrar middleware útil en [rack](https://github.com/rack/rack/tree/master/lib/rack), [rack-contrib](https://github.com/rack/rack-contrib#readme), 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 ``` ## 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. ### 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 podés tener una aplicación Sinatra por proceso Ruby. Si tenés planificado usar más, cambiá al estilo modular. Al mismo tiempo, ten en cuenta que no hay ninguna razón por la cuál no puedas mezclar los estilos clásico y modular. A continuación se detallan las diferencias (sútiles) entre las configuraciones de ambos estilos:
Configuración Clásica Modular
app_file archivo que carga sinatra archivo con la subclase de Sinatra::Base
run $0 == app_file false
logging true false
method_override true false
inline_templates true false
static true File.exist?(public_folder)
### Sirviendo una Aplicación Modular Las dos opciones más comunes para iniciar una aplicación modular son, iniciarla activamente con `run!`: ```ruby # mi_app.rb require 'sinatra/base' class MiApp < Sinatra::Base # ... código de la app ... # iniciar el servidor si el archivo fue ejecutado directamente run! if app_file == $0 end ``` Iniciar con: ```shell ruby mi_app.rb ``` O, con un archivo `config.ru`, que permite usar cualquier handler Rack: ```ruby # config.ru require './mi_app' run MiApp ``` Después ejecutar: ```shell rackup -p 4567 ``` ### Usando una Aplicación Clásica con un Archivo config.ru Escribí el archivo de tu aplicación: ```ruby # app.rb require 'sinatra' get '/' do 'Hola mundo!' end ``` Y el `config.ru` correspondiente: ```ruby require './app' run Sinatra::Application ``` ### ¿Cuándo usar config.ru? Indicadores de que probablemente querés usar `config.ru`: * Querés realizar el deploy con un handler Rack distinto (Passenger, Unicorn, Heroku, ...). * Querés usar más de una subclase de `Sinatra::Base`. * Querés usar Sinatra únicamente para middleware, pero no como un endpoint. No hay necesidad de utilizar un archivo `config.ru` exclusivamente porque tenés una aplicación modular, y no necesitás una aplicación modular para iniciarla con `config.ru`. ### Utilizando Sinatra como Middleware Sinatra no solo es capaz de usar otro Rack middleware, sino que a su vez, cualquier aplicación Sinatra puede ser agregada delante de un endpoint Rack como middleware. Este endpoint puede ser otra aplicación Sinatra, o cualquier aplicación basada en Rack (Rails/Ramaze/Camping/...): ```ruby require 'sinatra/base' class PantallaDeLogin < Sinatra::Base enable :sessions get('/login') { haml :login } post('/login') do if params['nombre'] == 'admin' && params['password'] == 'admin' session['nombre_de_usuario'] = params['nombre'] else redirect '/login' end end end class MiApp < Sinatra::Base # el middleware se ejecutará antes que los filtros use PantallaDeLogin before do unless session['nombre_de_usuario'] halt "Acceso denegado, por favor iniciá sesión." end end get('/') { "Hola #{session['nombre_de_usuario']}." } end ``` ### Creación Dinámica de Aplicaciones Puede que en algunas ocasiones quieras crear nuevas aplicaciones en tiempo de ejecución sin tener que asignarlas a una constante. Para esto tenés `Sinatra.new`: ```ruby require 'sinatra/base' mi_app = Sinatra.new { get('/') { "hola" } } mi_app.run! ``` Acepta como argumento opcional una aplicación desde la que se heredará: ```ruby # config.ru require 'sinatra/base' controller = Sinatra.new do enable :logging helpers MisHelpers end map('/a') do run Sinatra.new(controller) { get('/') { 'a' } } end map('/b') do run Sinatra.new(controller) { get('/') { 'b' } } end ``` Construir aplicaciones de esta forma resulta especialmente útil para testear extensiones Sinatra o para usar Sinatra en tus librerías. Por otro lado, hace extremadamente sencillo usar Sinatra como middleware: ```ruby require 'sinatra/base' use Sinatra do get('/') { ... } end run ProyectoRails::Application ``` ## Ámbitos y Ligaduras El ámbito en el que te encontrás determina que métodos y variables están disponibles. ### Ámbito de Aplicación/Clase Cada aplicación Sinatra es una subclase de `Sinatra::Base`. Si estás usando el DSL de top-level (`require 'sinatra'`), entonces esta clase es `Sinatra::Application`, de otra manera es la subclase que creaste explícitamente. Al nivel de la clase tenés métodos como `get` o `before`, pero no podés acceder a los objetos `request` o `session`, ya que hay una única clase de la aplicación para todas las peticiones. Las opciones creadas utilizando `set` son métodos al nivel de la clase: ```ruby class MiApp < Sinatra::Base # Ey, estoy en el ámbito de la aplicación! set :foo, 42 foo # => 42 get '/foo' do # Hey, ya no estoy en el ámbito de la aplicación! end end ``` Tenés la ligadura al ámbito de la aplicación dentro de: * El cuerpo de la clase de tu aplicación * Métodos definidos por extensiones * El bloque pasado a `helpers` * Procs/bloques usados como el valor para `set` Este ámbito puede alcanzarse de las siguientes maneras: * A través del objeto pasado a los bloques de configuración (`configure { |c| ...}`) * Llamando a `settings` desde dentro del ámbito de la petición ### Ámbito de Petición/Instancia Para cada petición entrante, una nueva instancia de la clase de tu aplicación es creada y todos los bloques de rutas son ejecutados en ese ámbito. Desde este ámbito podés acceder a los objetos `request` y `session` o llamar a los métodos de renderización como `erb` o `haml`. Podés acceder al ámbito de la aplicación desde el ámbito de la petición utilizando `settings`: ```ruby class MiApp < Sinatra::Base # Ey, estoy en el ámbito de la aplicación! get '/definir_ruta/:nombre' do # Ámbito de petición para '/definir_ruta/:nombre' @valor = 42 settings.get("/#{params['nombre']}") do # Ámbito de petición para "/#{params['nombre']}" @valor # => nil (no es la misma petición) end "Ruta definida!" end end ``` Tenés la ligadura al ámbito de la petición dentro de: * bloques pasados a get/head/post/put/delete/options * filtros before/after * métodos ayudantes * plantillas/vistas ### Ámbito de Delegación El ámbito de delegación solo reenvía métodos al ámbito de clase. De cualquier manera, no se comporta 100% como el ámbito de clase porque no tenés la ligadura de la clase: únicamente métodos marcados explícitamente para delegación están disponibles y no compartís variables/estado con el ámbito de clase (léase: tenés un `self` diferente). Podés agregar delegaciones de método llamando a `Sinatra::Delegator.delegate :nombre_del_metodo`. Tenés la ligadura al ámbito de delegación dentro de: * La ligadura del top-level, si hiciste `require "sinatra"` * Un objeto extendido con el mixin `Sinatra::Delegator` Hechale un vistazo al código: acá está el [Sinatra::Delegator mixin](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/base.rb#L1609-1633) que [extiende el objeto main](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/main.rb#L28-30). ## Línea de Comandos Las aplicaciones Sinatra pueden ser ejecutadas directamente: ```shell ruby miapp.rb [-h] [-x] [-e ENTORNO] [-p PUERTO] [-o HOST] [-s MANEJADOR] ``` Las opciones son: ``` -h # ayuda -p # asigna el puerto (4567 es usado por defecto) -o # asigna el host (0.0.0.0 es usado por defecto) -e # asigna el entorno (development es usado por defecto) -s # especifica el servidor/manejador rack (thin es usado por defecto) -x # activa el mutex lock (está desactivado por defecto) ``` ### Multi-threading _Basado en [esta respuesta en StackOverflow][so-answer] 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 ``` [so-answer]: http://stackoverflow.com/questions/6278817/is-sinatra-multi-threaded/6282999#6282999) ## Versiones de Ruby Soportadas Las siguientes versiones de Ruby son soportadas oficialmente:
Ruby 1.8.7
1.8.7 es soportado completamente. Sin embargo, si no hay nada que te lo prohiba, te recomendamos que uses 1.9.2 o cambies a JRuby o Rubinius. No se dejará de dar soporte a 1.8.7 hasta Sinatra 2.0 y Ruby 2.0, aunque si se libera la versión 1.8.8 de Ruby las cosas podrían llegar a cambiar. Sin embargo, que eso ocurra es muy poco probable, e incluso el caso de que lo haga, puede que se siga dando soporte a 1.8.7. Hemos dejado de soportar Ruby 1.8.6. Si querés ejecutar Sinatra sobre 1.8.6, podés utilizar la versión 1.2, pero ten en cuenta que una vez que Sinatra 1.4.0 sea liberado, ya no se corregirán errores por más que se reciban reportes de los mismos.
Ruby 1.9.2
1.9.2 es soportado y recomendado. No uses 1.9.2p0, porque se producen fallos de segmentación cuando se ejecuta Sinatra. El soporte se mantendrá al menos hasta que se libere la versión 1.9.4/2.0 de Ruby. El soporte para la última versión de la serie 1.9 se mantendrá mientras lo haga el equipo principal de Ruby.
Ruby 1.9.3
1.9.3 es soportado y recomendado. Ten en cuenta que el cambio a 1.9.3 desde una versión anterior va a invalidar todas las sesiones.
Rubinius
Rubinius es soportado oficialmente (Rubinius >= 1.2.4). Todo funciona correctamente, incluyendo los lenguajes de plantillas. La próxima versión, 2.0, también es soportada, incluyendo el modo 1.9.
JRuby
JRuby es soportado oficialmente (JRuby >= 1.6.7). No se conocen problemas con librerías de plantillas de terceras partes. Sin embargo, si elegís usar JRuby, deberías examinar sus Rack handlers porque el servidor web Thin no es soportado completamente. El soporte de JRuby para extensiones C se encuentra en una etapa experimental, sin embargo, de momento, solamente RDiscount, Redcarpet, RedCloth y Yajl, así como Thin y Mongrel se ven afectadas.
Siempre le prestamos atención a las nuevas versiones de Ruby. Las siguientes implementaciones de Ruby no se encuentran soportadas oficialmente. De cualquier manera, pueden ejecutar Sinatra: * Versiones anteriores de JRuby y Rubinius * Ruby Enterprise Edition * MacRuby, Maglev e IronRuby * Ruby 1.9.0 y 1.9.1 (pero no te recomendamos que los uses) No 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. Nuestro servidor CI también se ejecuta sobre ruby-head (que será la próxima versión 2.1.0) y la rama 1.9.4. Como están en movimiento constante, no podemos garantizar nada. De todas formas, podés contar con que tanto 1.9.4-p0 como 2.1.0-p0 sea soportadas. Sinatra debería funcionar en cualquier sistema operativo soportado por la implementación de Ruby elegida. En este momento, no vas a poder ejecutar Sinatra en Cardinal, SmallRuby, BlueRuby o cualquier versión de Ruby anterior a 1.8.7. ## A la Vanguardia Si querés usar el código de Sinatra más reciente, sentite libre de ejecutar tu aplicación sobre la rama master, en general es bastante estable. También liberamos prereleases de vez en cuando, así, podés hacer: ```shell gem install sinatra --pre ``` Para obtener algunas de las últimas características. ### Con Bundler Esta es la manera recomendada para ejecutar tu aplicación sobre la última versión de Sinatra usando [Bundler](http://bundler.io). Primero, instalá Bundler si no lo hiciste todavía: ```shell gem install bundler ``` Después, en el directorio de tu proyecto, creá un archivo `Gemfile`: ```ruby source :rubygems gem 'sinatra', :git => "git://github.com/sinatra/sinatra.git" # otras dependencias gem 'haml' # por ejemplo, si usás haml gem 'activerecord', '~> 3.0' # quizás también necesités ActiveRecord 3.x ``` Tené en cuenta que tenés que listar todas las dependencias directas de tu aplicación. No es necesario listar las dependencias de Sinatra (Rack y Tilt) porque Bundler las agrega directamente. Ahora podés arrancar tu aplicación así: ```shell bundle exec ruby miapp.rb ``` ### Con Git Cloná el repositorio localmente y ejecutá tu aplicación, asegurándote que el directorio `sinatra/lib` esté en el `$LOAD_PATH`: ```shell cd miapp git clone git://github.com/sinatra/sinatra.git ruby -Isinatra/lib miapp.rb ``` Para actualizar el código fuente de Sinatra en el futuro: ```shell cd miapp/sinatra git pull ``` ### Instalación Global Podés construir la gem vos mismo: ```shell git clone git://github.com/sinatra/sinatra.git cd sinatra rake sinatra.gemspec rake install ``` Si instalás tus gems como root, el último paso debería ser ```shell sudo rake install ``` ## Versionado Sinatra utiliza el [Versionado Semántico](http://semver.org/), siguiendo las especificaciones SemVer y SemVerTag. ## Lecturas Recomendadas * [Sito web del proyecto](http://www.sinatrarb.com/) - Documentación adicional, noticias, y enlaces a otros recursos. * [Contribuyendo](http://www.sinatrarb.com/contributing) - ¿Encontraste un error?. ¿Necesitás ayuda?. ¿Tenés un parche?. * [Seguimiento de problemas](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 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-1.4.8/README.pt-pt.md0000644000004100000410000004245613044044066015553 0ustar www-datawww-data# Sinatra *Atenção: Este documento é apenas uma tradução da versão em inglês e pode estar desatualizado.* Sinatra é uma [DSL](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 /\A\/ola\/([\w]+)\z/ 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 (`RACK_ENV` environment variável) é definido para `:production`: ```ruby configure :production do ... end ``` Correndo quando o ambiente é definido para `:production` ou `:test`: ```ruby configure :production, :test do ... end ``` ## Lidar com Erros Lida-se com erros no mesmo contexto das rotas e filtros before, o que signifca que `haml`, `erb`, etc, estão disponíveis. ### Não Encontrado Quando um `Sinatra::NotFound` exception é levantado, ou o código de status da reposta é 404, o manipulador `not_found` é invocado: ```ruby not_found do 'Isto está longe de ser encontrado' end ``` ### Erro O manipulador `error` é invocado sempre que uma exceção é lançada a partir de um bloco de rota ou um filtro. O objecto da exceção pode ser obtido a partir da variável Rack `sinatra.error`: ```ruby error do 'Peço desculpa, houve um erro desagradável - ' + env['sinatra.error'].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-1.4.8/README.ru.md0000644000004100000410000032564713044044066015143 0ustar www-datawww-data# 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` и вложенные раскладки (layout)](#Шаблоны-с-yield-и-вложенные-раскладки-layout) * [Включённые шаблоны](#Включённые-шаблоны) * [Именованные шаблоны](#Именованные-шаблоны) * [Привязка файловых расширений](#Привязка-файловых-расширений) * [Добавление собственного движка рендеринга](#Добавление-собственного-движка-рендеринга) * [Фильтры](#Фильтры) * [Методы-помощники](#Методы-помощники) * [Использование сессий](#Использование-сессий) * [Прерывание](#Прерывание) * [Передача](#Передача) * [Вызов другого маршрута](#Вызов-другого-маршрута) * [Задание тела, кода и заголовков ответа](#Задание-тела-кода-и-заголовков-ответа) * [Стриминг ответов](#Стриминг-ответов) * [Логирование](#Логирование) * [Mime-типы](#mime-типы) * [Генерирование URL](#Генерирование-url) * [Перенаправление (редирект)](#Перенаправление-редирект) * [Управление кэшированием](#Управление-кэшированием) * [Отправка файлов](#Отправка-файлов) * [Доступ к объекту запроса](#Доступ-к-объекту-запроса) * [Вложения](#Вложения) * [Работа со временем и датами](#Работа-со-временем-и-датами) * [Поиск шаблонов](#Поиск-шаблонов) * [Конфигурация](#Конфигурация) * [Настройка защиты от атак](#Настройка-защиты-от-атак) * [Доступные настройки](#Доступные-настройки) * [Режим, окружение](#Режим-окружение) * [Обработка ошибок](#Обработка-ошибок) * [Not Found](#not-found) * [Error](#error) * [Rack "прослойки"](#rack-прослойки) * [Тестирование](#Тестирование) * [Sinatra::Base — "прослойки", библиотеки и модульные приложения](#sinatrabase--прослойки-библиотеки-и-модульные-приложения) * [Модульные приложения против классических](#Модульные-приложения-против-классических) * [Запуск модульных приложений](#Запуск-модульных-приложений) * [Запуск классических приложений с config.ru](#Запуск-классических-приложений-с-configru) * [Когда использовать config.ru?](#Когда-использовать-configru) * [Использование Sinatra в качестве "прослойки"](#Использование-sinatra-в-качестве-прослойки) * [Создание приложений "на лету"](#Создание-приложений-на-лету) * [Области видимости и привязка](#Области-видимости-и-привязка) * [Область видимости приложения / класса](#Область-видимости-приложения--класса) * [Область видимости запроса / экземпляра](#Область-видимости-запроса--экземпляра) * [Область видимости делегирования](#Область-видимости-делегирования) * [Командная строка](#Командная-строка) * [Multi-threading](#multi-threading) * [Системные требования](#Системные-требования) * [На острие](#На-острие) * [С помощью Bundler](#С-помощью-bundler) * [Вручную](#Вручную) * [Установка глобально](#Установка-глобально) * [Версии](#Версии) * [Дальнейшее чтение](#Дальнейшее-чтение) *Внимание: Этот документ является переводом английской версии и может быть устаревшим* 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) Рекомендуется также установить Thin, сделать это можно командой: `gem install thin`. Thin — это более производительный и функциональный сервер для разработки приложений на Sinatra. ## Маршруты В Sinatra маршрут — это пара: <HTTP метод> и <шаблон URL>. Каждый маршрут связан с блоком кода: ```ruby get '/' do # .. что-то показать .. end post '/' do # .. что-то создать .. end put '/' do # .. что-то заменить .. end patch '/' do # .. что-то изменить .. end delete '/' do # .. что-то удалить .. end options '/' do # .. что-то ответить .. end link '/' do .. что-то подключить .. end unlink '/' do .. что-то отключить .. end ``` Маршруты сверяются с запросом в порядке очередности их записи в файле приложения. Первый же совпавший с запросом маршрут и будет вызван. Шаблоны маршрутов могут включать в себя именованные параметры, доступные в xэше `params`: ```ruby get '/hello/:name' do # соответствует "GET /hello/foo" и "GET /hello/bar", # где params['name'] 'foo' или 'bar' "Hello #{params['name']}!" end ``` Также можно использовать именованные параметры в качестве переменных блока: ```ruby get '/hello/:name' do |n| "Hello #{n}!" end ``` Шаблоны маршрутов также могут включать в себя splat (или '*' маску, обозначающую любой символ) параметры, доступные в массиве `params['splat']`: ```ruby get '/say/*/to/*' do # соответствует /say/hello/to/world params['splat'] # => ["hello", "world"] end get '/download/*.*' do # соответствует /download/path/to/file.xml params['splat'] # => ["path/to/file", "xml"] end ``` Или с параметрами блока: ```ruby get '/download/*.*' do |path, ext| [path, ext] # => ["path/to/file", "xml"] end ``` Регулярные выражения в качестве шаблонов маршрутов: ```ruby get /\A\/hello\/([\w]+)\z/ 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 ``` Кстати, если вы не отключите защиту от обратного пути в директориях (path traversal, см. ниже), путь запроса может быть изменен до начала поиска подходящего маршрута. ### Условия Маршруты могут включать различные условия совпадений, например, клиентское приложение (user agent): ```ruby get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do "You're using Songbird version #{params['agent'][0]}" end get '/foo' do # соответствует не-songbird браузерам end ``` Другими доступными условиями являются `host_name` и `provides`: ```ruby get '/', :host_name => /^admin\./ do "Admin Area, Access denied!" end get '/', :provides => 'html' do haml :index end get '/', :provides => ['rss', 'atom', 'xml'] do builder :feed end ``` Вы можете задать собственные условия: ```ruby set(:probability) { |value| condition { rand <= value } } get '/win_a_car', :probability => 0.1 do "You won!" end get '/win_a_car' do "Sorry, you lost." end ``` Для условия, которое принимает несколько параметров, используйте звездочку: ```ruby set(:auth) do |*roles| # <- обратите внимание на звездочку condition do unless logged_in? && roles.any? {|role| current_user.in_role? role } redirect "/login/", 303 end end end get "/my/account/", :auth => [:user, :admin] do "Your Account Details" end get "/only/admin/", :auth => :admin do "Only admins are allowed here!" end ``` ### Возвращаемые значения Возвращаемое значение блока маршрута ограничивается телом ответа, которое будет передано HTTP клиенту, или следующей "прослойкой" (middleware) в Rack стеке. Чаще всего это строка, как в примерах выше. Но также приемлемы и другие значения. Вы можете вернуть любой объект, который будет либо корректным Rack ответом, объектом Rack body, либо кодом состояния HTTP: * массив с тремя переменными: `[код (Fixnum), заголовки (Hash), тело ответа (должно отвечать на #each)]`; * массив с двумя переменными: `[код (Fixnum), тело ответа (должно отвечать на #each)]`; * объект, отвечающий на `#each`, который передает только строковые типы данных в этот блок; * Fixnum, представляющий код состояния HTTP. Таким образом, легко можно реализовать, например, поточный пример: ```ruby class Stream def each 100.times { |i| yield "#{i}\n" } end end get('/') { Stream.new } ``` Вы также можете использовать метод `stream` (описываемый ниже), чтобы уменьшить количество дублируемого кода и держать логику стриминга прямо в маршруте. ### Собственные детекторы совпадений для маршрутов Как показано выше, Sinatra поставляется со встроенной поддержкой строк и регулярных выражений в качестве шаблонов URL. Но и это еще не все. Вы можете легко определить свои собственные детекторы совпадений (matchers) для маршрутов: ```ruby class AllButPattern Match = Struct.new(:captures) def initialize(except) @except = except @captures = Match.new([]) end def match(str) @captures unless @except === str end end def all_but(pattern) AllButPattern.new(pattern) end get all_but("/index") do # ... end ``` Заметьте, что предыдущий пример, возможно, чересчур усложнен, потому что он может быть реализован так: ```ruby get // do pass if request.path_info == "/index" # ... end ``` Или с использованием негативного просмотра вперед: ```ruby get %r{^(?!/index$)} do # ... end ``` ## Статические файлы Статические файлы отдаются из `./public` директории. Вы можете указать другое место, используя опцию `:public_folder`: ```ruby set :public_folder, File.dirname(__FILE__) + '/static' ``` Учтите, что имя директории со статическими файлами не включено в URL. Например, файл `./public/css/style.css` будет доступен как `http://example.com/css/style.css`. Используйте опцию `:static_cache_control` (см. ниже), чтобы добавить заголовок `Cache-Control`. ## Представления / Шаблоны Каждый шаблонизатор представлен своим собственным методом. Эти методы попросту возвращают строку: ```ruby get '/' do erb :index end ``` Отобразит `views/index.erb`. Вместо имени шаблона вы так же можете передавать непосредственно само содержимое шаблона: ```ruby get '/' do code = "<%= Time.now %>" erb code end ``` Эти методы принимают второй аргумент, хеш с опциями: ```ruby get '/' do erb :index, :layout => :post end ``` Отобразит `views/index.erb`, вложенным в `views/post.erb` (по умолчанию: `views/layout.erb`, если существует). Любые опции, не понимаемые Sinatra, будут переданы в шаблонизатор: ```ruby get '/' do haml :index, :format => :html5 end ``` Вы также можете задавать опции для шаблонизаторов в общем: ```ruby set :haml, :format => :html5 get '/' do haml :index end ``` Опции, переданные в метод, переопределяют опции, заданные с помощью `set`. Доступные опции:
locals
Список локальных переменных, передаваемых в документ. Например: erb "<%= foo %>", :locals => {:foo => "bar"}
default_encoding
Кодировка, которую следует использовать, если не удалось определить оригинальную. По умолчанию: settings.default_encoding.
views
Директория с шаблонами. По умолчанию: settings.views.
layout
Использовать или нет лэйаут (true или false). Если же значение Symbol, то указывает, какой шаблон использовать в качестве лэйаута. Например: erb :index, :layout => !request.xhr?
content_type
Content-Type отображенного шаблона. По умолчанию: задается шаблонизатором.
scope
Область видимости, в которой рендерятся шаблоны. По умолчанию: экземпляр приложения. Если вы измените эту опцию, то переменные экземпляра и методы-помощники станут недоступными в ваших шаблонах.
layout_engine
Шаблонизатор, который следует использовать для отображения лэйаута. Полезная опция для шаблонизаторов, в которых нет никакой поддержки лэйаутов. По умолчанию: тот же шаблонизатор, что используется и для самого шаблона. Пример: set :rdoc, :layout_engine => :erb
По умолчанию считается, что шаблоны находятся в директории `./views`. Чтобы использовать другую директорию с шаблонами: ```ruby set :views, settings.root + '/templates' ``` Важное замечание: вы всегда должны ссылаться на шаблоны с помощью символов (Symbol), даже когда они в поддиректории (в этом случае используйте `:'subdir/template'`). Вы должны использовать символы, потому что иначе шаблонизаторы попросту отображают любые строки, переданные им. ### Буквальные шаблоны ```ruby get '/' do haml '%div.title Hello World' end ``` Отобразит шаблон, переданный строкой. ### Доступные шаблонизаторы Некоторые языки шаблонов имеют несколько реализаций. Чтобы указать, какую реализацию использовать, вам следует просто подключить нужную библиотеку: ```ruby require 'rdiscount' # или require 'bluecloth' get('/') { markdown :index } ``` #### Haml шаблоны
Зависимости haml
Расширения файлов .haml
Пример haml :index, :format => :html5
#### Erb шаблоны
Зависимости erubis или erb (включен в Ruby)
Расширения файлов .erb, .rhtml or .erubis (только Erubis)
Пример erb :index
#### Builder шаблоны
Зависимости builder
Расширения файлов .builder
Пример builder { |xml| xml.em "hi" }
Блок также используется и для встроенных шаблонов (см. пример). #### Nokogiri шаблоны
Зависимости nokogiri
Расширения файлов .nokogiri
Пример nokogiri { |xml| xml.em "hi" }
Блок также используется и для встроенных шаблонов (см. пример). #### Sass шаблоны
Зависимости sass
Расширения файлов .sass
Пример sass :stylesheet, :style => :expanded
#### SCSS шаблоны
Зависимости sass
Расширения файлов .scss
Пример scss :stylesheet, :style => :expanded
#### Less шаблоны
Зависимости less
Расширения файлов .less
Пример less :stylesheet
#### Liquid шаблоны
Зависимости liquid
Расширения файлов .liquid
Пример liquid :index, :locals => { :key => 'value' }
Так как в Liquid шаблонах невозможно вызывать методы из Ruby (кроме `yield`), то вы почти всегда будете передавать в шаблон локальные переменные. #### Markdown шаблоны
Зависимости Любая из библиотек: RDiscount, RedCarpet, BlueCloth, kramdown, maruku
Расширения файлов .markdown, .mkd and .md
Пример markdown :index, :layout_engine => :erb
В Markdown невозможно вызывать методы или передавать локальные переменные. Следовательно, вам, скорее всего, придется использовать этот шаблон совместно с другим шаблонизатором: ```ruby erb :overview, :locals => { :text => markdown(:introduction) } ``` Заметьте, что вы можете вызывать метод `markdown` из других шаблонов: ```ruby %h1 Hello From Haml! %p= markdown(:greetings) ``` Вы не можете вызывать Ruby из Markdown, соответственно, вы не можете использовать лэйауты на Markdown. Тем не менее, есть возможность использовать один шаблонизатор для отображения шаблона, а другой для лэйаута с помощью опции `:layout_engine`. #### Textile шаблоны
Зависимости RedCloth
Расширения файлов .textile
Пример textile :index, :layout_engine => :erb
В Textile невозможно вызывать методы или передавать локальные переменные. Следовательно, вам, скорее всего, придется использовать этот шаблон совместно с другим шаблонизатором: ```ruby erb :overview, :locals => { :text => textile(:introduction) } ``` Заметьте, что вы можете вызывать метод `textile` из других шаблонов: ```ruby %h1 Hello From Haml! %p= textile(:greetings) ``` Вы не можете вызывать Ruby из Textile, соответственно, вы не можете использовать лэйауты на Textile. Тем не менее, есть возможность использовать один шаблонизатор для отображения шаблона, а другой для лэйаута с помощью опции `:layout_engine`. #### RDoc шаблоны
Зависимости RDoc
Расширения файлов .rdoc
Пример rdoc :README, :layout_engine => :erb
В RDoc невозможно вызывать методы или передавать локальные переменные. Следовательно, вам, скорее всего, придется использовать этот шаблон совместно с другим шаблонизатором: ```ruby erb :overview, :locals => { :text => rdoc(:introduction) } ``` Заметьте, что вы можете вызывать метод `rdoc` из других шаблонов: ```ruby %h1 Hello From Haml! %p= rdoc(:greetings) ``` Вы не можете вызывать Ruby из RDoc, соответственно, вы не можете использовать лэйауты на RDoc. Тем не менее, есть возможность использовать один шаблонизатор для отображения шаблона, а другой для лэйаута с помощью опции `:layout_engine`. #### 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/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`), то вы почти всегда будете передавать в шаблон локальные переменные. ### Доступ к переменным в шаблонах Шаблоны интерпретируются в том же контексте, что и обработчики маршрутов. Переменные экземпляра, установленные в процессе обработки маршрутов, будут доступны напрямую в шаблонах: ```ruby get '/:id' do @foo = Foo.find(params['id']) haml '%h1= @foo.name' end ``` Либо установите их через хеш локальных переменных: ```ruby get '/:id' do foo = Foo.find(params['id']) haml '%h1= bar.name', :locals => { :bar => foo } end ``` Это обычный подход, когда шаблоны рендерятся как части других шаблонов. ### Шаблоны с `yield` и вложенные раскладки (layout) Раскладка (layout) обычно представляет собой шаблон, который исполняет `yield`. Такой шаблон может быть либо использован с помощью опции `:template`, как описано выше, либо он может быть дополнен блоком: ```ruby erb :post, :layout => false do erb :index end ``` Эти инструкции в основном эквивалентны `erb :index, :layout => :post`. Передача блоков интерпретирующим шаблоны методам наиболее полезна для создания вложенных раскладок: ```ruby erb :main_layout, :layout => false do erb :admin_layout do erb :user end end ``` Это же самое может быть сделано короче: ```ruby erb :admin_layout, :layout => :main_layout do erb :user end ``` В настоящее время, следующие интерпретирующие шаблоны методы принимают блок: `erb`, `haml`, `liquid`, `slim `, `wlang`. Общий метод заполнения шаблонов `render` также принимает блок. ### Включённые шаблоны Шаблоны также могут быть определены в конце исходного файла: ```ruby require 'sinatra' get '/' do haml :index end __END__ @@ layout %html = yield @@ index %div.title Hello world. ``` Заметьте: включённые шаблоны, определенные в исходном файле, который подключил Sinatra, будут загружены автоматически. Вызовите `enable :inline_templates` напрямую, если используете включённые шаблоны в других файлах. ### Именованные шаблоны Шаблоны также могут быть определены при помощи `template` метода: ```ruby template :layout do "%html\n =yield\n" end template :index do '%div.title Hello World!' end get '/' do haml :index end ``` Если шаблон с именем "layout" существует, то он будет использоваться каждый раз при рендеринге. Вы можете отключать лэйаут в каждом конкретном случае с помощью `:layout => false` или отключить его для всего приложения: `set :haml, :layout => false`: ```ruby get '/' do haml :index, :layout => !request.xhr? end ``` ### Привязка файловых расширений Чтобы связать расширение файла с движком рендеринга, используйте `Tilt.register`. Например, если вы хотите использовать расширение `tt` для шаблонов Textile: ```ruby Tilt.register :tt, Tilt[:textile] ``` ### Добавление собственного движка рендеринга Сначала зарегистрируйте свой движок в Tilt, а затем создайте метод, отвечающий за рендеринг: ```ruby Tilt.register :myat, MyAwesomeTemplateEngine helpers do def myat(*args) render(:myat, *args) end end get '/' do myat :index end ``` Отобразит `./views/index.myat`. Чтобы узнать больше о Tilt, смотрите https://github.com/rtomayko/tilt ## Фильтры `before`-фильтры выполняются перед каждым запросом в том же контексте, что и маршруты, и могут изменять как запрос, так и ответ на него. Переменные экземпляра, установленные в фильтрах, доступны в маршрутах и шаблонах: ```ruby before do @note = 'Hi!' request.path_info = '/foo/bar/baz' end get '/foo/*' do @note #=> 'Hi!' params['splat'] #=> 'bar/baz' end ``` `after`-фильтры выполняются после каждого запроса в том же контексте и могут изменять как запрос, так и ответ на него. Переменные экземпляра, установленные в `before`-фильтрах и маршрутах, будут доступны в `after`-фильтрах: ```ruby after do puts response.status end ``` Заметьте: если вы используете метод `body`, а не просто возвращаете строку из маршрута, то тело ответа не будет доступно в `after`-фильтрах, так как оно будет сгенерировано позднее. Фильтры могут использовать шаблоны URL и будут интерпретированы, только если путь запроса совпадет с этим шаблоном: ```ruby before '/protected/*' do authenticate! end after '/create/:slug' do |slug| session['last_slug'] = slug end ``` Как и маршруты, фильтры могут использовать условия: ```ruby before :agent => /Songbird/ do # ... end after '/blog/*', :host_name => 'example.com' do # ... end ``` ## Методы-помощники Используйте метод `helpers`, чтобы определить методы-помощники, которые в дальнейшем можно будет использовать в обработчиках маршрутов и шаблонах: ```ruby helpers do def bar(name) "#{name}bar" end end get '/:name' do bar(params['name']) end ``` Также методы-помощники могут быть заданы в отдельных модулях: ```ruby module FooUtils def foo(name) "#{name}foo" end end module BarUtils def bar(name) "#{name}bar" end end helpers FooUtils, BarUtils ``` Эффект равносилен включению модулей в класс приложения. ### Использование сессий Сессия используется, чтобы сохранять состояние между запросами. Если эта опция включена, то у вас будет один хеш сессии на одну пользовательскую сессию: ```ruby enable :sessions get '/' do "value = " << session['value'].inspect end get '/:value' do session['value'] = params['value'] end ``` Заметьте, что при использовании `enable :sessions` все данные сохраняются в куках (cookies). Это может быть не совсем то, что вы хотите (например, сохранение больших объемов данных увеличит ваш трафик). В таком случае вы можете использовать альтернативную Rack "прослойку" (middleware), реализующую механизм сессий. Для этого *не надо* вызывать `enable :sessions`, вместо этого следует подключить ее так же, как и любую другую "прослойку": ```ruby use Rack::Session::Pool, :expire_after => 2592000 get '/' do "value = " << session['value'].inspect end get '/:value' do session['value'] = params['value'] end ``` Для повышения безопасности данные сессии в куках подписываются секретным ключом. Секретный ключ генерируется Sinatra. Тем не менее, так как этот ключ будет меняться с каждым запуском приложения, вы, возможно, захотите установить ключ вручную, чтобы у всех экземпляров вашего приложения был один и тот же ключ: ```ruby set :session_secret, 'super secret' ``` Если вы хотите больше настроек для сессий, вы можете задать их, передав хеш опций в параметр `sessions`: ```ruby set :sessions, :domain => 'foo.com' ``` Чтобы сделать сессию доступной другим приложениям, размещенным на поддоменах foo.com, добавьте *.* перед доменом: ```ruby set :sessions, :domain => '.foo.com' ``` ### Прерывание Чтобы незамедлительно прервать обработку запроса внутри фильтра или маршрута, используйте: ```ruby halt ``` Можно также указать статус при прерывании: ```ruby halt 410 ``` Тело: ```ruby halt 'this will be the body' ``` И то, и другое: ```ruby halt 401, 'go away!' ``` Можно указать заголовки: ```ruby halt 402, {'Content-Type' => 'text/plain'}, 'revenge' ``` И, конечно, можно использовать шаблоны с `halt`: ```ruby halt erb(:error) ``` ### Передача Маршрут может передать обработку запроса следующему совпадающему маршруту, используя `pass`: ```ruby get '/guess/:who' do pass unless params['who'] == 'Frank' 'You got me!' end get '/guess/*' do 'You missed!' end ``` Блок маршрута сразу же прерывается, и контроль переходит к следующему совпадающему маршруту. Если соответствующий маршрут не найден, то ответом на запрос будет 404. ### Вызов другого маршрута Иногда `pass` не подходит, например, если вы хотите получить результат вызова другого обработчика маршрута. В таком случае просто используйте `call`: ```ruby get '/foo' do status, headers, body = call env.merge("PATH_INFO" => '/bar') [status, headers, body.map(&:upcase)] end get '/bar' do "bar" end ``` Заметьте, что в предыдущем примере можно облегчить тестирование и повысить производительность, перенеся `"bar"` в метод-помощник, используемый и в `/foo`, и в `/bar`. Если вы хотите, чтобы запрос был отправлен в тот же экземпляр приложения, а не в его копию, используйте `call!` вместо `call`. Если хотите узнать больше о `call`, смотрите спецификацию Rack. ### Задание тела, кода и заголовков ответа Хорошим тоном является установка кода состояния HTTP и тела ответа в возвращаемом значении обработчика маршрута. Тем не менее, в некоторых ситуациях вам, возможно, понадобится задать тело ответа в произвольной точке потока исполнения. Вы можете сделать это с помощью метода-помощника `body`. Если вы задействуете метод `body`, то вы можете использовать его и в дальнейшем, чтобы получить доступ к телу ответа. ```ruby get '/foo' do body "bar" end after do puts body end ``` Также можно передать блок в метод `body`, который затем будет вызван обработчиком Rack (такой подход может быть использован для реализации поточного ответа, см. "Возвращаемые значения"). Аналогично вы можете установить код ответа и его заголовки: ```ruby get '/foo' do status 418 headers \ "Allow" => "BREW, POST, GET, PROPFIND, WHEN", "Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt" body "I'm a tea pot!" end ``` Как и `body`, методы `headers` и `status`, вызванные без аргументов, возвращают свои текущие значения. ### Стриминг ответов Иногда требуется начать отправлять данные клиенту прямо в процессе генерирования частей этих данных. В особых случаях требуется постоянно отправлять данные до тех пор, пока клиент не закроет соединение. Вы можете использовать метод `stream` вместо написания собственных "оберток". ```ruby get '/' do stream do |out| out << "It's gonna be legen -\n" sleep 0.5 out << " (wait for it) \n" sleep 1 out << "- dary!\n" end end ``` Что позволяет вам реализовать стриминговые API, [Server Sent Events](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 ``` ### Логирование В области видимости запроса метод `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): ```ruby require 'rack/cache' require 'sinatra' use Rack::Cache get '/' do cache_control :public, :max_age => 36000 sleep 5 "hello" end ``` Используйте опцию `:static_cache_control` (см. ниже), чтобы добавить заголовок `Cache-Control` к статическим файлам. В соответствии с RFC 2616 ваше приложение должно вести себя по-разному, когда заголовки If-Match или If-None-Match имеют значение `*`, в зависимости от того, существует или нет запрашиваемый ресурс. Sinatra предполагает, что ресурсы, к которым обращаются с помощью безопасных (GET) и идемпотентных (PUT) методов, уже существуют, а остальные ресурсы (к которым обращаются, например, с помощью POST) считает новыми. Вы можете изменить данное поведение с помощью опции `:new_resource`: ```ruby get '/create' do etag '', :new_resource => true Article.create erb :new_article end ``` Если вы хотите использовать weak ETag, задайте опцию `:kind`: ```ruby etag '', :new_resource => true, :kind => :weak ``` ### Отправка файлов Для отправки файлов пользователю вы можете использовать метод `send_file`: ```ruby get '/' do send_file 'foo.png' end ``` Этот метод имеет несколько опций: ```ruby send_file 'foo.png', :type => :jpg ``` Возможные опции:
filename
имя файла, по умолчанию: реальное имя файла.
last_modified
значение для заголовка Last-Modified, по умолчанию: mtime (время изменения) файла.
type
тип файла, по умолчанию: определяется по расширению файла.
disposition
используется для заголовка Content-Disposition, возможные значения: nil (по умолчанию), :attachment и :inline.
length
значения для заголовка Content-Length, по умолчанию: размер файла.
status
Код ответа. Полезно, когда отдается статический файл в качестве страницы с сообщением об ошибке.
Этот метод будет использовать возможности Rack сервера для отправки файлов, если они доступны, в противном случае будет напрямую отдавать файл из Ruby процесса. Метод `send_file` также обеспечивает автоматическую обработку частичных (range) запросов с помощью Sinatra. ### Доступ к объекту запроса Объект входящего запроса доступен на уровне обработки запроса (в фильтрах, маршрутах, обработчиках ошибок) с помощью `request` метода: ```ruby # приложение запущено на http://example.com/example get '/foo' do t = %w[text/css text/html application/javascript] request.accept # ['text/html', '*/*'] request.accept? 'text/xml' # true request.preferred_type(t) # 'text/html' request.body # тело запроса, посланное клиентом (см. ниже) request.scheme # "http" request.script_name # "/example" request.path_info # "/foo" request.port # 80 request.request_method # "GET" request.query_string # "" request.content_length # длина тела запроса request.media_type # медиатип тела запроса request.host # "example.com" request.get? # true (есть аналоги для других методов HTTP) request.form_data? # false request["some_param"] # значение параметра some_param. Шорткат для хеша params request.referrer # источник запроса клиента либо '/' request.user_agent # user agent (используется для :agent условия) request.cookies # хеш, содержащий cookies браузера request.xhr? # является ли запрос ajax запросом? request.url # "http://example.com/example/foo" request.path # "/example/foo" request.ip # IP-адрес клиента request.secure? # false (true, если запрос сделан через SSL) request.forwarded? # true (если сервер работает за обратным прокси) request.env # "сырой" env хеш, полученный Rack end ``` Некоторые опции, такие как `script_name` или `path_info`, доступны для изменения: ```ruby before { request.path_info = "/" } get "/" do "all requests end up here" end ``` `request.body` является IO или StringIO объектом: ```ruby post "/api" do request.body.rewind # в случае, если кто-то уже прочитал тело запроса data = JSON.parse request.body.read "Hello #{data['name']}!" end ``` ### Вложения Вы можете использовать метод `attachment`, чтобы сказать браузеру, что ответ сервера должен быть сохранен на диск, а не отображен: ```ruby get '/' do attachment "store it!" end ``` Вы также можете указать имя файла: ```ruby get '/' do attachment "info.txt" "store it!" end ``` ### Работа со временем и датами Sinatra предлагает метод-помощник `time_for`, который из заданного значения создает объект Time. Он также может конвертировать `DateTime`, `Date` и подобные классы: ```ruby get '/' do pass if Time.now > time_for('Dec 23, 2012') "still time" end ``` Этот метод используется внутри Sinatra методами `expires`, `last_modified` и им подобными. Поэтому вы легко можете изменить и дополнить поведение этих методов, переопределив `time_for` в своем приложении: ```ruby helpers do def time_for(value) case value when :yesterday then Time.now - 24*60*60 when :tomorrow then Time.now + 24*60*60 else super end end end get '/' do last_modified :yesterday expires :tomorrow "hello" end ``` ### Поиск шаблонов Для поиска шаблонов и их последующего рендеринга используется метод `find_template`: ```ruby find_template settings.views, 'foo', Tilt[:haml] do |file| puts "could be #{file}" end ``` Это не слишком полезный пример. Зато полезен тот факт, что вы можете переопределить этот метод, чтобы использовать свой собственный механизм поиска. Например, если вы хотите, чтобы можно было использовать несколько директорий с шаблонами: ```ruby set :views, ['views', 'templates'] helpers do def find_template(views, name, engine, &block) Array(views).each { |v| super(v, name, engine, &block) } end end ``` Другой пример, в котором используются разные директории для движков рендеринга: ```ruby set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views' helpers do def find_template(views, name, engine, &block) _, folder = views.detect { |k,v| engine == Tilt[k] } folder ||= views[:default] super(folder, name, engine, &block) end end ``` Вы можете легко вынести этот код в расширение и поделиться им с остальными! Заметьте, что `find_template` не проверяет, существует ли файл на самом деле, а вызывает заданный блок для всех возможных путей. Дело тут не в производительности, дело в том, что `render` вызовет `break`, как только файл не будет найден. Содержимое и местонахождение шаблонов будет закэшировано, если приложение запущено не в режиме разработки (`set :environment, :development`). Вы должны помнить об этих нюансах, если пишите по-настоящему "сумасшедший" метод. ## Конфигурация Этот блок исполняется один раз при старте в любом окружении, режиме (environment): ```ruby configure do # задание одной опции set :option, 'value' # устанавливаем несколько опций set :a => 1, :b => 2 # то же самое, что и `set :option, true` enable :option # то же самое, что и `set :option, false` disable :option # у вас могут быть "динамические" опции с блоками set(:css_dir) { File.join(views, 'css') } end ``` Будет запущено, когда окружение (RACK_ENV переменная) `:production`: ```ruby configure :production do ... end ``` Будет запущено, когда окружение `:production` или `:test`: ```ruby configure :production, :test do ... end ``` Вы можете получить доступ к этим опциям с помощью `settings`: ```ruby configure do set :foo, 'bar' end get '/' do settings.foo? # => true settings.foo # => 'bar' ... end ``` ### Настройка защиты от атак Sinatra использует [Rack::Protection](https://github.com/sinatra/rack-protection#readme) для защиты приложения от простых атак. Вы можете легко выключить эту защиту (что сделает ваше приложение чрезвычайно уязвимым): ```ruby disable :protection ``` Чтобы пропустить какой-либо уровень защиты, передайте хеш опций в параметр `protection`: ```ruby set :protection, :except => :path_traversal ``` Вы также можете отключить сразу несколько уровней защиты: ```ruby set :protection, :except => [:path_traversal, :session_hijacking] ``` ### Доступные настройки
absolute_redirects
если отключено, то Sinatra будет позволять использование относительных перенаправлений, но при этом перестанет соответствовать RFC 2616 (HTTP 1.1), который разрешает только абсолютные перенаправления.
Включайте эту опцию, если ваше приложение работает за обратным прокси, который настроен не совсем корректно. Обратите внимание, метод url все равно будет генерировать абсолютные URL, если вы не передадите false вторым аргументом.
Отключено по умолчанию.
add_charset
mime-типы, к которым метод content_type будет автоматически добавлять информацию о кодировке. Вам следует добавлять значения к этой опции вместо ее переопределения: settings.add_charset << "application/foobar"
app_file
путь к главному файлу приложения, используется для нахождения корневой директории проекта, директорий с шаблонами и статическими файлами, вложенных шаблонов.
bind
используемый IP-адрес (по умолчанию: 0.0.0.0). Используется только встроенным сервером.
default_encoding
кодировка, если неизвестна (по умолчанию: "utf-8").
dump_errors
отображать ошибки в логе.
environment
текущее окружение, по умолчанию, значение ENV['RACK_ENV'] или "development", если ENV['RACK_ENV'] недоступна.
logging
использовать логер.
lock
создает блокировку для каждого запроса, которая гарантирует обработку только одного запроса в текущий момент времени в Ruby процессе.
Включайте, если ваше приложение не потоко-безопасно (thread-safe). Отключено по умолчанию.
method_override
использовать "магический" параметр _method, для поддержки PUT/DELETE форм в браузерах, которые не поддерживают эти методы.
port
порт, на котором будет работать сервер. Используется только встроенным сервером.
prefixed_redirects
добавлять или нет параметр request.script_name к редиректам, если не задан абсолютный путь. Таким образом, redirect '/foo' будет вести себя как redirect to('/foo'). Отключено по умолчанию.
protection
включена или нет защита от атак. Смотрите секцию выше.
public_dir
Алиас для public_folder.
public_folder
путь к директории, откуда будут раздаваться статические файлы. Используется, только если включена раздача статических файлов (см. опцию static ниже).
reload_templates
перезагружать или нет шаблоны на каждый запрос. Включено в режиме разработки.
root
путь к корневой директории проекта.
raise_errors
выбрасывать исключения (будет останавливать приложение). По умолчанию включено только в окружении test.
run
если включено, Sinatra будет самостоятельно запускать веб-сервер. Не включайте, если используете rackup или аналогичные средства.
running
работает ли сейчас встроенный сервер? Не меняйте эту опцию!
server
сервер или список серверов, которые следует использовать в качестве встроенного сервера. По умолчанию: ['thin', 'mongrel', 'webrick'], порядок задает приоритет.
sessions
включить сессии на основе кук (cookie) на базе Rack::Session::Cookie. Смотрите секцию "Использование сессий" выше.
show_exceptions
показывать исключения/стек вызовов (stack trace) в браузере. По умолчанию включено только в окружении development.
Может быть установлено в :after_handler для запуска специфичной для приложения обработки ошибок, перед показом трассировки стека в браузере.
static
должна ли Sinatra осуществлять раздачу статических файлов.
Отключите, когда используете какой-либо веб-сервер для этой цели.
Отключение значительно улучшит производительность приложения.
По умолчанию включено в классических и отключено в модульных приложениях.
static_cache_control
когда Sinatra отдает статические файлы, используйте эту опцию, чтобы добавить им заголовок Cache-Control. Для этого используется метод-помощник cache_control. По умолчанию отключено.
Используйте массив, когда надо задать несколько значений: set :static_cache_control, [:public, :max_age => 300]
threaded
если включено, то Thin будет использовать EventMachine.defer для обработки запросов.
traps
должна ли Синатра обрабатывать системные сигналы или нет.
views
путь к директории с шаблонами.
## Режим, окружение Есть 3 предопределенных режима, окружения: `"development"`, `"production"` и `"test"`. Режим может быть задан через переменную окружения `RACK_ENV`. Значение по умолчанию — `"development"`. В этом режиме работы все шаблоны перезагружаются между запросами. А также задаются специальные обработчики `not_found` и `error`, чтобы вы могли увидеть стек вызовов. В окружениях `"production"` и `"test"` шаблоны по умолчанию кэшируются. Для запуска приложения в определенном окружении используйте ключ `-e` ``` ruby my_app.rb -e [ENVIRONMENT] ``` Вы можете использовать предопределенные методы `development?`, `test?` и +production?, чтобы определить текущее окружение. ## Обработка ошибок Обработчики ошибок исполняются в том же контексте, что и маршруты, и `before`-фильтры, а это означает, что всякие прелести вроде `haml`, `erb`, `halt` и т.д. доступны и им. ### Not Found Когда выброшено исключение `Sinatra::NotFound`, или кодом ответа является 404, то будет вызван `not_found` обработчик: ```ruby not_found do 'This is nowhere to be found.' end ``` ### Error Обработчик ошибок `error` будет вызван, когда исключение выброшено из блока маршрута, либо из фильтра. Объект-исключение доступен как переменная `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](http://rack.github.io/), минимальный стандартный интерфейс для веб-фреймворков на Ruby. Одной из самых интересных для разработчиков возможностей Rack является поддержка "прослоек" ("middleware") — компонентов, находящихся "между" сервером и вашим приложением, которые отслеживают и/или манипулируют HTTP запросами/ответами для предоставления различной функциональности. В Sinatra очень просто использовать такие "прослойки" с помощью метода `use`: ```ruby require 'sinatra' require 'my_custom_middleware' use Rack::Lint use MyCustomMiddleware get '/hello' do 'Hello World' end ``` Семантика `use` идентична той, что определена для [Rack::Builder](http://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. [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::Base — "прослойки", библиотеки и модульные приложения Описание своего приложения самым простейшим способом (с помощью DSL верхнего уровня, классический стиль) отлично работает для крохотных приложений. В таких случаях используется конфигурация, рассчитанная на микро-приложения (единственный файл приложения, `./public` и `./views` директории, логирование, страница информации об исключении и т.д.). Тем не менее, такой метод имеет множество недостатков при создании компонентов, таких как Rack middleware ("прослоек"), Rails metal, простых библиотек с серверными компонентами, расширений Sinatra. И тут на помощь приходит `Sinatra::Base`: ```ruby require 'sinatra/base' class MyApp < Sinatra::Base set :sessions, true set :foo, 'bar' get '/' do 'Hello world!' end end ``` Методы, доступные `Sinatra::Base` подклассам идентичны тем, что доступны приложениям в DSL верхнего уровня. Большинство таких приложений могут быть конвертированы в `Sinatra::Base` компоненты с помощью двух модификаций: * Вы должны подключать `sinatra/base` вместо `sinatra`, иначе все методы, предоставляемые Sinatra, будут импортированы в глобальное пространство имен. * Поместите все маршруты, обработчики ошибок, фильтры и опции в подкласс `Sinatra::Base`. `Sinatra::Base` — это чистый лист. Большинство опций, включая встроенный сервер, по умолчанию отключены. Смотрите [Опции и конфигурация](http://www.sinatrarb.com/configuration.html) для детальной информации об опциях и их поведении. ### Модульные приложения против классических Вопреки всеобщему убеждению, в классическом стиле (самом простом) нет ничего плохого. Если этот стиль подходит вашему приложению, вы не обязаны переписывать его в модульное приложение. Основным недостатком классического стиля является тот факт, что у вас может быть только одно приложение Sinatra на один процесс Ruby. Если вы планируете использовать больше, переключайтесь на модульный стиль. Вы можете смело смешивать модульный и классический стили. Переходя с одного стиля на другой, примите во внимание следующие изменения в настройках: Опция Классический Модульный app_file файл с приложением файл с подклассом Sinatra::Base run $0 == app_file false logging true false method_override true false inline_templates true false static true File.exist?(public_folder) ### Запуск модульных приложений Есть два общепринятых способа запускать модульные приложения: запуск напрямую с помощью `run!`: ```ruby # my_app.rb require 'sinatra/base' class MyApp < Sinatra::Base # ... здесь код приложения ... # запускаем сервер, если исполняется текущий файл run! if app_file == $0 end ``` Затем: ``` ruby my_app.rb ``` Или с помощью конфигурационного файла `config.ru`, который позволяет использовать любой Rack-совместимый сервер приложений. ```ruby # config.ru require './my_app' run MyApp ``` Запускаем: ``` rackup -p 4567 ``` ### Запуск классических приложений с config.ru Файл приложения: ```ruby # app.rb require 'sinatra' get '/' do 'Hello world!' end ``` И соответствующий `config.ru`: ```ruby require './app' run Sinatra::Application ``` ### Когда использовать config.ru? Вот несколько причин, по которым вы, возможно, захотите использовать `config.ru`: * вы хотите разворачивать свое приложение на различных Rack-совместимых серверах (Passenger, Unicorn, Heroku, ...); * вы хотите использовать более одного подкласса `Sinatra::Base`; * вы хотите использовать Sinatra только в качестве "прослойки" Rack. **Совсем необязательно переходить на использование `config.ru` лишь потому, что вы стали использовать модульный стиль приложения. И необязательно использовать модульный стиль, чтобы запускать приложение с помощью `config.ru`.** ### Использование Sinatra в качестве "прослойки" Не только сама Sinatra может использовать "прослойки" Rack, но и любое Sinatra приложение само может быть добавлено к любому Rack endpoint в качестве "прослойки". Этим endpoint (конечной точкой) может быть другое Sinatra приложение, или приложение, основанное на Rack (Rails/Ramaze/Camping/...): ```ruby require 'sinatra/base' class LoginScreen < Sinatra::Base enable :sessions get('/login') { haml :login } post('/login') do if params['name'] == 'admin' && params['password'] == 'admin' session['user_name'] = params['name'] else redirect '/login' end end end class MyApp < Sinatra::Base # "прослойка" будет запущена перед фильтрами use LoginScreen before do unless session['user_name'] halt "Access denied, please login." end end get('/') { "Hello #{session['user_name']}." } end ``` ### Создание приложений "на лету" Иногда требуется создавать Sinatra приложения "на лету" (например, из другого приложения). Это возможно с помощью `Sinatra.new`: ```ruby require 'sinatra/base' my_app = Sinatra.new { get('/') { "hi" } } my_app.run! ``` Этот метод может принимать аргументом приложение, от которого следует наследоваться: ```ruby # config.ru require 'sinatra/base' controller = Sinatra.new do enable :logging helpers MyHelpers end map('/a') do run Sinatra.new(controller) { get('/') { 'a' } } end map('/b') do run Sinatra.new(controller) { get('/') { 'b' } } end ``` Это особенно полезно для тестирования расширений Sinatra и при использовании Sinatra внутри вашей библиотеки. Благодаря этому, использовать Sinatra как "прослойку" очень просто: ```ruby require 'sinatra/base' use Sinatra do get('/') { ... } end run RailsProject::Application ``` ## Области видимости и привязка Текущая область видимости определяет методы и переменные, доступные в данный момент. ### Область видимости приложения / класса Любое Sinatra приложение соответствует подклассу `Sinatra::Base`. Если вы используете DSL верхнего уровня (`require 'sinatra'`), то этим классом будет `Sinatra::Application`, иначе это будет подкласс, который вы создали вручную. На уровне класса вам будут доступны такие методы, как `get` или `before`, но вы не сможете получить доступ к объектам `request` или `session`, так как существует только один класс приложения для всех запросов. Опции, созданные с помощью `set`, являются методами уровня класса: ```ruby class MyApp < Sinatra::Base # Я в области видимости приложения! set :foo, 42 foo # => 42 get '/foo' do # Я больше не в области видимости приложения! end end ``` У вас будет область видимости приложения внутри: * тела вашего класса приложения; * методов, определенных расширениями; * блока, переданного в `helpers`; * блоков, использованных как значения для `set`; * блока, переданного в `Sinatra.new`. Вы можете получить доступ к объекту области видимости (классу приложения) следующими способами: * через объект, переданный блокам конфигурации (`configure { |c| ... }`); * `settings` внутри области видимости запроса. ### Область видимости запроса / экземпляра Для каждого входящего запроса будет создан новый экземпляр вашего приложения, и все блоки обработчика будут запущены в этом контексте. В этой области видимости вам доступны `request` и `session` объекты, вызовы методов рендеринга, такие как `erb` или `haml`. Вы можете получить доступ к области видимости приложения из контекста запроса, используя метод-помощник `settings`: ```ruby class MyApp < Sinatra::Base # Я в области видимости приложения! get '/define_route/:name' do # Область видимости запроса '/define_route/:name' @value = 42 settings.get("/#{params['name']}") do # Область видимости запроса "/#{params['name']}" @value # => nil (другой запрос) end "Route defined!" end end ``` У вас будет область видимости запроса в: * get/head/post/put/delete/options блоках; * before/after фильтрах; * методах-помощниках; * шаблонах/отображениях. ### Область видимости делегирования Область видимости делегирования просто перенаправляет методы в область видимости класса. Однако, она не полностью ведет себя как область видимости класса, так как у вас нет привязки к классу. Только методы, явно помеченные для делегирования, будут доступны, а переменных/состояний области видимости класса не будет (иначе говоря, у вас будет другой `self` объект). Вы можете непосредственно добавить методы делегирования, используя `Sinatra::Delegator.delegate :method_name`. У вас будет контекст делегирования внутри: * привязки верхнего уровня, если вы сделали `require 'sinatra'`; * объекта, расширенного с помощью `Sinatra::Delegator`. Посмотрите сами в код: вот [примесь Sinatra::Delegator](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/base.rb#L1609-1633) [расширяет главный объект](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/main.rb#L28-30). ## Командная строка Sinatra приложения могут быть запущены напрямую: ``` ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER] ``` Опции включают: ``` -h # раздел помощи -p # указание порта (по умолчанию 4567) -o # указание хоста (по умолчанию 0.0.0.0) -e # указание окружения, режима (по умолчанию development) -s # указание rack сервера/обработчика (по умолчанию thin) -x # включить мьютекс-блокировку (по умолчанию выключена) ``` ### Multi-threading _Данный раздел является перефразированным [ответом пользователя Konstantin][so-answer] на 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 ``` [so-answer]: http://stackoverflow.com/questions/6278817/is-sinatra-multi-threaded/6282999#6282999) ## Системные требования Следующие версии Ruby официально поддерживаются:
Ruby 1.8.7
1.8.7 полностью поддерживается, тем не менее, если вас ничто не держит на этой версии, рекомендуем обновиться до 1.9.2 или перейти на JRuby или Rubinius. Поддержка 1.8.7 не будет прекращена до выхода Sinatra 2.0 и Ruby 2.0, разве что в случае релиза 1.8.8 (что маловероятно). Но даже тогда, возможно, поддержка не будет прекращена. Ruby 1.8.6 больше не поддерживается. Если вы хотите использовать 1.8.6, откатитесь до Sinatra 1.2, которая будет получать все исправления ошибок до тех пор, пока не будет выпущена Sinatra 1.4.0.
Ruby 1.9.2
1.9.2 полностью поддерживается и рекомендована к использованию. Не используйте 1.9.2p0, известно, что эта версия очень нестабильна при использовании Sinatra. Эта версия будет поддерживаться по крайней мере до выхода Ruby 1.9.4/2.0, а поддержка последней версии 1.9 будет осуществляться до тех пор, пока она поддерживается командой разработчиков Ruby.
Ruby 1.9.3
1.9.3 полностью поддерживается. Заметьте, что переход на 1.9.3 с ранних версий сделает недействительными все сессии.
Rubinius
Rubinius официально поддерживается (Rubinius >= 1.2.4), всё, включая все языки шаблонов, работает. Предстоящий релиз 2.0 также поддерживается.
JRuby
JRuby официально поддерживается (JRuby >= 1.6.5). Нет никаких проблем с использованием альтернативных шаблонов. Тем не менее, если вы выбираете JRuby, то, пожалуйста, посмотрите на JRuby Rack-серверы, так как Thin не поддерживается полностью на JRuby. Поддержка расширений на C в JRuby все еще экспериментальная, что на данный момент затрагивает только RDiscount, Redcarpet и RedCloth.
Мы также следим за предстоящими к выходу версиями Ruby. Следующие реализации Ruby не поддерживаются официально, но известно, что на них запускается Sinatra: * старые версии JRuby и Rubinius; * Ruby Enterprise Edition; * MacRuby, Maglev, IronRuby; * Ruby 1.9.0 и 1.9.1 (настоятельно не рекомендуются к использованию). То, что версия официально не поддерживается, означает, что, если что-то не работает на этой версии, а на поддерживаемой работает — это не наша проблема, а их. Мы также запускаем наши CI-тесты на версии Ruby, находящейся в разработке (предстоящей 2.0.0), и на 1.9.4, но мы не можем ничего гарантировать, так как они находятся в разработке. Предполагается, что 1.9.4p0 и 2.0.0p0 будут поддерживаться. Sinatra должна работать на любой операционной системе, в которой есть одна из указанных выше версий Ruby. Пока невозможно запустить Sinatra на Cardinal, SmallRuby, BlueRuby и на любой версии Ruby до 1.8.7. ## На острие Если вы хотите использовать самый последний код Sinatra, не бойтесь запускать свое приложение вместе с кодом из master ветки Sinatra, она весьма стабильна. Мы также время от времени выпускаем предварительные версии, так что вы можете делать так: ``` gem install sinatra --pre ``` Чтобы воспользоваться некоторыми самыми последними возможностями. ### С помощью Bundler Если вы хотите запускать свое приложение с последней версией Sinatra, то рекомендуем использовать [Bundler](http://bundler.io). Сначала установите Bundler, если у вас его еще нет: ``` gem install bundler ``` Затем создайте файл `Gemfile` в директории вашего проекта: ```ruby source :rubygems gem 'sinatra', :git => "git://github.com/sinatra/sinatra.git" # другие зависимости gem 'haml' # например, если используете haml gem 'activerecord', '~> 3.0' # может быть, вам нужен и ActiveRecord 3.x ``` Обратите внимание, вам нужно будет указывать все зависимости вашего приложения в этом файле. Однако, непосредственные зависимости Sinatra (Rack и Tilt) Bundler автоматически скачает и добавит. Теперь вы можете запускать свое приложение так: ``` bundle exec ruby myapp.rb ``` ### Вручную Создайте локальный клон репозитория и запускайте свое приложение с `sinatra/lib` директорией в `$LOAD_PATH`: ``` cd myapp git clone git://github.com/sinatra/sinatra.git ruby -Isinatra/lib myapp.rb ``` Чтобы обновить исходники Sinatra: ``` cd myapp/sinatra git pull ``` ### Установка глобально Вы можете самостоятельно собрать gem: ``` git clone git://github.com/sinatra/sinatra.git cd sinatra rake sinatra.gemspec rake install ``` Если вы устанавливаете пакеты (gem) от пользователя root, то вашим последним шагом должна быть команда ``` sudo rake install ``` ## Версии Sinatra использует [Semantic Versioning](http://semver.org/), SemVer и SemVerTag. ## Дальнейшее чтение * [Веб-сайт проекта](http://www.sinatrarb.com/) — Дополнительная документация, новости и ссылки на другие ресурсы. * [Участие в проекте](http://www.sinatrarb.com/contributing) — Обнаружили баг? Нужна помощь? Написали патч? * [Отслеживание проблем/ошибок](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) на http://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-1.4.8/lib/0000755000004100000410000000000013044044066013764 5ustar www-datawww-datasinatra-1.4.8/lib/sinatra.rb0000644000004100000410000000006113044044066015747 0ustar www-datawww-datarequire 'sinatra/main' enable :inline_templates sinatra-1.4.8/lib/sinatra/0000755000004100000410000000000013044044066015425 5ustar www-datawww-datasinatra-1.4.8/lib/sinatra/main.rb0000644000004100000410000000233013044044066016674 0ustar www-datawww-datarequire 'sinatra/base' module Sinatra class Application < Base # we assume that the first file that requires 'sinatra' is the # app_file. all other path related options are calculated based # on this path by default. set :app_file, caller_files.first || $0 set :run, Proc.new { File.expand_path($0) == File.expand_path(app_file) } if run? && ARGV.any? require 'optparse' OptionParser.new { |op| op.on('-p port', 'set the port (default is 4567)') { |val| set :port, Integer(val) } op.on('-o addr', "set the host (default is #{bind})") { |val| set :bind, val } op.on('-e env', 'set the environment (default is development)') { |val| set :environment, val.to_sym } op.on('-s server', 'specify rack server/handler (default is thin)') { |val| set :server, val } op.on('-x', 'turn on the mutex lock (default is off)') { set :lock, true } }.parse!(ARGV.dup) end end at_exit { Application.run! if $!.nil? && Application.run? } end # include would include the module in Object # extend only extends the `main` object extend Sinatra::Delegator class Rack::Builder include Sinatra::Delegator end sinatra-1.4.8/lib/sinatra/images/0000755000004100000410000000000013044044066016672 5ustar www-datawww-datasinatra-1.4.8/lib/sinatra/images/404.png0000644000004100000410000004471513044044066017722 0ustar www-datawww-dataPNG  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-1.4.8/lib/sinatra/images/500.png0000644000004100000410000005747213044044066017723 0ustar www-datawww-dataPNG  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-1.4.8/lib/sinatra/ext.rb0000644000004100000410000000064613044044066016560 0ustar www-datawww-data# This can be removed once rack/rack@2fd9df71 is released module Sinatra module Ext def self.get_handler(str) begin ::Object.const_get("Object", false) def self._const_get(str, inherit = true) Rack::Handler.const_get(str, inherit) end rescue def self._const_get(str, inherit = true) Rack::Handler.const_get(str) end end end end end sinatra-1.4.8/lib/sinatra/show_exceptions.rb0000644000004100000410000002746613044044066021212 0ustar www-datawww-databegin require 'rack/show_exceptions' rescue LoadError require 'rack/showexceptions' end 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 @template = ERB.new(TEMPLATE) 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" exception = dump_exception(e) else content_type = "text/html" exception = pretty(env, e) end env["rack.errors"] = errors # Post 893a2c50 in rack/rack, the #pretty method above, implemented in # Rack::ShowExceptions, returns a String instead of an array. body = Array(exception) [ 500, { "Content-Type" => content_type, "Content-Length" => Rack::Utils.bytesize(body.join).to_s }, body ] end private def prefers_plain_text?(env) !(Request.new(env).preferred_type("text/plain","text/html") == "text/html") && [/curl/].index{|item| item =~ env["HTTP_USER_AGENT"]} end def frame_class(frame) if frame.filename =~ /lib\/sinatra.*\.rb/ "framework" elsif (defined?(Gem) && frame.filename.include?(Gem.dir)) || frame.filename =~ /\/bin\/(\w+)$/ "system" else "app" end end TEMPLATE = <<-HTML # :nodoc: <%=h exception.class %> at <%=h path %>

BACKTRACE

(expand)

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

GET

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

No GET data.

<% end %>

POST

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

No POST data.

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

No cookie data.

<% end %>

Rack ENV

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

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

HTML end end sinatra-1.4.8/lib/sinatra/version.rb0000644000004100000410000000004713044044066017440 0ustar www-datawww-datamodule Sinatra VERSION = '1.4.8' end sinatra-1.4.8/lib/sinatra/base.rb0000644000004100000410000020152113044044066016665 0ustar www-datawww-data# external dependencies require 'rack' require 'tilt' require 'rack/protection' # stdlib dependencies require 'thread' require 'time' require 'uri' # other files we need require 'sinatra/show_exceptions' require 'sinatra/ext' 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 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, 205, 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 + Rack::Utils.bytesize(p) }.to_s end [status.to_i, headers, result] end private def calculate_content_length? headers["Content-Type"] and not headers["Content-Length"] and Array === body end def drop_content_info? status.to_i / 100 == 1 or drop_body? end def drop_body? 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 NotFound < NameError #:nodoc: def http_status; 404 end end # Methods available to routes, before/after filters, and views. module Helpers # Set or retrieve the response status code. def status(value = nil) response.status = value if value response.status end # Set or retrieve the response body. When a block is given, # evaluation is deferred until the body is read with #each. def body(value = nil, &block) if block_given? def block.each; yield(call) end response.body = block elsif value headers.delete 'Content-Length' unless request.head? || value.is_a?(Rack::File) || value.is_a?(Stream) response.body = value else response.body end end # Halt processing and redirect to the URI provided. def redirect(uri, *args) if env['HTTP_VERSION'] == 'HTTP/1.1' and env["REQUEST_METHOD"] != 'GET' status 303 else status 302 end # According to RFC 2616 section 14.30, "the field value consists of a # single absolute URI" response['Location'] = uri(uri.to_s, settings.absolute_redirects?, settings.prefixed_redirects?) halt(*args) end # Generates the absolute URI for a given path in the app. # Takes Rack routers and reverse proxies into account. def uri(addr = nil, absolute = true, add_script_name = true) return addr if addr =~ /\A[A-z][A-z0-9\+\.\-]*:/ uri = [host = ""] if absolute host << "http#{'s' if request.secure?}://" if request.forwarded? or request.port != (request.secure? ? 443 : 80) host << request.host_with_port else host << request.host end end uri << request.script_name.to_s if add_script_name uri << (addr ? addr : request.path_info).to_s File.join uri end alias url uri alias to uri # Halt processing and return the error status provided. def error(code, body = nil) code, body = 500, code.to_str if code.respond_to? :to_str response.body = body unless body.nil? halt code end # Halt processing and return a 404 Not Found. def not_found(body = nil) error 404, body end # Set multiple response headers with Hash. def headers(hash = nil) response.headers.merge! hash if hash response.headers end # Access the underlying Rack session. def session request.session end # Access shared logger object. def logger request.logger end # Look up a media type by file extension in Rack's mime registry. def mime_type(type) Base.mime_type(type) end # Set the Content-Type of the response body given a media type or file # extension. def content_type(type = nil, params = {}) return response['Content-Type'] unless type default = params.delete :default mime_type = mime_type(type) || default fail "Unknown media type: %p" % type if mime_type.nil? mime_type = mime_type.dup unless params.include? :charset or settings.add_charset.all? { |p| not p === mime_type } params[:charset] = params.delete('charset') || settings.default_encoding end params.delete :charset if mime_type.include? 'charset' unless params.empty? mime_type << (mime_type.include?(';') ? ', ' : ';') mime_type << params.map do |key, val| val = val.inspect if val =~ /[";,]/ "#{key}=#{val}" end.join(', ') end response['Content-Type'] = mime_type end # Set the Content-Disposition to "attachment" with the specified filename, # instructing the user agents to prompt to save. def attachment(filename = nil, disposition = 'attachment') response['Content-Disposition'] = disposition.to_s if filename params = '; filename="%s"' % File.basename(filename) response['Content-Disposition'] << params ext = File.extname(filename) content_type(ext) unless response['Content-Type'] or ext.empty? end end # Use the contents of the file at +path+ as the response body. def send_file(path, opts = {}) if opts[:type] or not response['Content-Type'] content_type opts[:type] || File.extname(path), :default => 'application/octet-stream' end disposition = opts[:disposition] filename = opts[:filename] disposition = 'attachment' if disposition.nil? and filename filename = path if filename.nil? attachment(filename, disposition) if disposition last_modified opts[:last_modified] if opts[:last_modified] file = Rack::File.new nil file.path = path result = file.serving env result[1].each { |k,v| headers[k] ||= v } headers['Content-Length'] = result[1]['Content-Length'] 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, :min_stale, :s_max_age). # # cache_control :public, :must_revalidate, :max_age => 60 # => Cache-Control: public, must-revalidate, max-age=60 # # See RFC 2616 / 14.9 for more on standard cache control directives: # http://tools.ietf.org/html/rfc2616#section-14.9.1 def cache_control(*values) if values.last.kind_of?(Hash) hash = values.pop hash.reject! { |k,v| v == false } hash.reject! { |k,v| values << k if v == true } else hash = {} end values.map! { |value| value.to_s.tr('_','-') } hash.each do |key, value| key = key.to_s.tr('_', '-') value = value.to_i if key == "max-age" values << "#{key}=#{value}" end response['Cache-Control'] = values.join(', ') if values.any? end # Set the Expires header and Cache-Control/max-age directive. Amount # can be an integer number of seconds in the future or a Time object # indicating when the response should be considered "stale". The remaining # "values" arguments are passed to the #cache_control helper: # # expires 500, :public, :must_revalidate # => Cache-Control: public, must-revalidate, max-age=60 # => Expires: Mon, 08 Jun 2009 08:50:17 GMT # def expires(amount, *values) values << {} unless values.last.kind_of?(Hash) if amount.is_a? Integer time = Time.now + amount.to_i max_age = amount else time = time_for amount max_age = time - Time.now end values.last.merge!(:max_age => max_age) cache_control(*values) response['Expires'] = time.httpdate end # Set the last modified time of the resource (HTTP 'Last-Modified' header) # and halt if conditional GET matches. The +time+ argument is a Time, # DateTime, or other object that responds to +to_time+. # # When the current request includes an 'If-Modified-Since' header that is # equal or later than the time specified, execution is immediately halted # with a '304 Not Modified' response. def last_modified(time) return unless time time = time_for time response['Last-Modified'] = time.httpdate return if env['HTTP_IF_NONE_MATCH'] if status == 200 and env['HTTP_IF_MODIFIED_SINCE'] # compare based on seconds since epoch since = Time.httpdate(env['HTTP_IF_MODIFIED_SINCE']).to_i halt 304 if since >= time.to_i end if (success? or status == 412) and env['HTTP_IF_UNMODIFIED_SINCE'] # compare based on seconds since epoch since = Time.httpdate(env['HTTP_IF_UNMODIFIED_SINCE']).to_i halt 412 if since < time.to_i end rescue ArgumentError end 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 # Generates a Time object from the given value. # Used by #expires and #last_modified. def time_for(value) if value.respond_to? :to_time value.to_time elsif value.is_a? Time value elsif value.respond_to? :new_offset # DateTime#to_time does the same on 1.9 d = value.new_offset 0 t = Time.utc d.year, d.mon, d.mday, d.hour, d.min, d.sec + d.sec_fraction t.getlocal elsif value.respond_to? :mday # Date#to_time does the same on 1.9 Time.local(value.year, value.mon, value.mday) elsif value.is_a? Numeric Time.at value else Time.parse value.to_s end rescue ArgumentError => boom raise boom rescue Exception raise ArgumentError, "unable to convert #{value.inspect} to a Time object" end private # Helper method checking if a ETag value list includes the current ETag. def etag_matches?(list, new_resource = request.post?) return !new_resource if list == '*' list.to_s.split(/\s*,\s*/).include? response['ETag'] end def with_params(temp_params) original, @params = @params, temp_params yield ensure @params = original if original end end private # Template rendering methods. Each method takes the name of a template # to render as a Symbol and returns a String with the rendered output, # as well as an optional hash with additional options. # # `template` is either the name or path of the template as symbol # (Use `:'subdir/myview'` for views in subdirectories), or a string # that will be rendered. # # Possible options are: # :content_type The content type to use, same arguments as content_type. # :layout If set to something falsy, no layout is rendered, otherwise # the specified layout is used (Ignored for `sass` and `less`) # :layout_engine Engine to use for rendering the layout. # :locals A hash with local variables that should be available # in the template # :scope If set, template is evaluate with the binding of the given # object rather than the application instance. # :views Views directory to use. module Templates module ContentTyped attr_accessor :content_type end def initialize super @default_layout = :layout @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 = {}) 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}") if Tilt.respond_to?(:mappings) Tilt.mappings.each do |ext, engines| next unless ext != @preferred_extension and engines.include? engine yield ::File.join(views, "#{name}.#{ext}") end else Tilt.default_mapping.extensions_for(engine).each do |ext| yield ::File.join(views, "#{name}.#{ext}") unless ext == @preferred_extension end 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(:content_type) || options.delete(:default_content_type) layout_engine = options.delete(:layout_engine) || engine scope = options.delete(:scope) || self options.delete(:layout) # set some defaults options[:outvar] ||= '@_out_buf' options[:default_encoding] ||= settings.default_encoding # compile and render template begin layout_was = @default_layout @default_layout = false template = compile_template(engine, data, options, views) output = template.render(scope, locals, &block) ensure @default_layout = layout_was end # render layout if layout options = options.merge(:views => views, :layout => false, :eat_errors => eat_errors, :scope => scope). 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 path, line = settings.caller_locations.first template.new(path, line.to_i, options, &body) else raise ArgumentError, "Sorry, don't know how to render #{data.inspect}." end end end end # Base class for all Sinatra applications and middleware. class Base include Rack::Utils include Helpers include Templates URI_INSTANCE = URI.const_defined?(:Parser) ? URI::Parser.new : URI attr_accessor :app, :env, :request, :response, :params attr_reader :template_cache def initialize(app = nil) super() @app = app @template_cache = Tilt::Cache.new yield self if block_given? end # Rack call interface. def call(env) dup.call!(env) end def call!(env) # :nodoc: @env = env @request = Request.new(env) @response = Response.new @params = indifferent_params(@request.params) template_cache.clear if settings.reload_templates force_encoding(@params) @response['Content-Type'] = nil invoke { dispatch! } invoke { error_block!(response.status) } unless @env['sinatra.error'] unless @response['Content-Type'] if Array === body and body[0].respond_to? :content_type content_type body[0].content_type else content_type :html end end @response.finish end # Access settings defined with Base.set. def self.settings self end # Access settings defined with Base.set. def settings self.class.settings end def options warn "Sinatra::Base#options is deprecated and will be removed, " \ "use #settings instead." settings end # Exit the current block, halts any further processing # of the request, and returns the specified response. def halt(*response) response = response.first if response.length == 1 throw :halt, response end # Pass control to the next matching route. # If there are no more matching routes, Sinatra will # return a 404 response. def pass(&block) throw :pass, block end # Forward the request to the downstream app -- middleware only. def forward fail "downstream app not set" unless @app.respond_to? :call status, headers, body = @app.call env @response.status = status @response.body = body @response.headers.merge! headers nil end private # Run filters defined on the class and all superclasses. def filter!(type, base = settings) filter! type, base.superclass if base.superclass.respond_to?(:filters) base.filters[type].each { |args| process_route(*args) } end # Run routes defined on the class and all superclasses. def route!(base = settings, pass_block = nil) if routes = base.routes[@request.request_method] routes.each do |pattern, keys, conditions, block| returned_pass_block = process_route(pattern, keys, conditions) do |*args| env['sinatra.route'] = block.instance_variable_get(:@route_name) 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, keys, conditions, block = nil, values = []) route = @request.path_info route = '/' if route.empty? and not settings.empty_path_info? return unless match = pattern.match(route) values += match.captures.map! { |v| force_encoding URI_INSTANCE.unescape(v) if v } if values.any? original, @params = params, params.merge('splat' => [], 'captures' => values) keys.zip(values) { |k,v| Array === @params[k] ? @params[k] << v : @params[k] = v if v } end catch(:pass) do conditions.each { |c| throw :pass if c.bind(self).call == false } block ? block[self, values] : yield(self, values) end ensure @params = original if original end # No matching route was found or all routes passed. The default # implementation is to forward the request downstream when running # as middleware (@app is non-nil); when no downstream app is set, raise # a NotFound exception. Subclasses can override this method to perform # custom route miss logic. def route_missing if @app forward else raise NotFound end end # Attempt to serve static files from public directory. Throws :halt when # a matching file is found, returns nil otherwise. def static!(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 # Enable string or symbol key access to the nested params hash. def indifferent_params(object) case object when Hash new_hash = indifferent_hash object.each { |key, value| new_hash[key] = indifferent_params(value) } new_hash when Array object.map { |item| indifferent_params(item) } else object end end # Creates a Hash with indifferent access. def indifferent_hash Hash.new {|hash,key| hash[key.to_s] if Symbol === key } end # Run the block with 'throw :halt' support and apply result to the response. def invoke res = catch(:halt) { yield } res = [res] if 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! invoke do static! if settings.static? && (request.get? || request.head?) filter! :before route! end rescue ::Exception => boom invoke { handle_exception!(boom) } ensure begin filter! :after unless env['sinatra.static_file'] rescue ::Exception => boom invoke { handle_exception!(boom) } unless @env['sinatra.error'] end end # Error handling during requests. def handle_exception!(boom) @env['sinatra.error'] = boom if boom.respond_to? :http_status status(boom.http_status) elsif settings.use_code? and boom.respond_to? :code and boom.code.between? 400, 599 status(boom.code) else status(500) end status(500) unless status.between? 400, 599 if server_error? dump_errors! boom if settings.dump_errors? raise boom if settings.show_exceptions? and settings.show_exceptions != :after_handler end if not_found? headers['X-Cascade'] = 'pass' if settings.x_cascade? body '

Not Found

' end res = error_block!(boom.class, boom) || error_block!(status, boom) return res if res or not server_error? raise boom if settings.raise_errors? or settings.show_exceptions? error_block! Exception, boom end # Find an custom error block for the key(s) specified. def error_block!(key, *block_params) base = settings while base.respond_to?(:errors) next base = base.superclass unless args_array = base.errors[key] args_array.reverse_each do |args| first = args == args_array.first args += [block_params] resp = process_route(*args) return resp unless resp.nil? && !first end end return false unless key.respond_to? :superclass and key.superclass < Exception error_block!(key.superclass, *block_params) end def dump_errors!(boom) msg = ["#{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)?\.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) if setter define_singleton(option, getter) if getter define_singleton("#{option}?", "!!#{option}") unless method_defined? "#{option}?" self end # Same as calling `set :option, true` for each of the given options. def enable(*opts) opts.each { |key| set(key, true) } end # Same as calling `set :option, false` for each of the given options. def disable(*opts) opts.each { |key| set(key, false) } end # Define a custom error handler. Optionally takes either an Exception # class, or an HTTP status code to specify which errors should be # handled. def error(*codes, &block) args = compile! "ERROR", //, block codes = codes.map { |c| Array(c) }.flatten codes << Exception if codes.empty? codes.each { |c| (@errors[c] ||= []) << args } end # Sugar for `error(404) { ... }` def not_found(&block) error(404, &block) error(Sinatra::NotFound, &block) end # Define a named template. The block must return the template source. def template(name, &block) filename, line = caller_locations.first templates[name] = [block, filename, line.to_i] end # Define the layout template. The block must return the template source. def layout(name = :layout, &block) template name, &block end # Load embedded templates from the file; uses the caller's __FILE__ # when no file is specified. def inline_templates=(file = nil) file = (file.nil? || file == true) ? (caller_files.first || File.expand_path($0)) : file begin io = ::IO.respond_to?(:binread) ? ::IO.binread(file) : ::IO.read(file) app, data = io.gsub("\r\n", "\n").split(/^__END__$/, 2) rescue Errno::ENOENT app, data = nil end if data if app and app =~ /([^\n]*\n)?#[^\n]*coding: *(\S+)/m encoding = $2 else encoding = settings.default_encoding end lines = app.count("\n") + 1 template = nil force_encoding data, encoding data.each_line do |line| lines += 1 if line =~ /^@@\s*(.*\S)\s*$/ template = force_encoding('', encoding) templates[$1.to_sym] = [template, file, lines] elsif template template << line end end end end # Lookup or register a mime type in Rack's mime registry. def mime_type(type, value = nil) return type if type.nil? return type.to_s if type.to_s.include?('/') type = ".#{type}" unless type.to_s[0] == ?. return Rack::Mime.mime_type(type, nil) unless value Rack::Mime::MIME_TYPES[type] = value end # provides all mime types matching type, including deprecated types: # mime_types :html # => ['text/html'] # mime_types :js # => ['application/javascript', 'text/javascript'] def mime_types(type) type = mime_type type type =~ /^application\/(xml|javascript)$/ ? [type, "text/#$1"] : [type] end # Define a before filter; runs before all requests within the same # context as route handlers and may access/modify the request and # response. def before(path = nil, options = {}, &block) add_filter(:before, path, options, &block) end # Define an after filter; runs after all requests within the same # context as route handlers and may access/modify the request and # response. def after(path = nil, options = {}, &block) add_filter(:after, path, options, &block) end # add a filter def add_filter(type, path = nil, options = {}, &block) path, options = //, path if path.respond_to?(:each_pair) filters[type] << compile!(type, path || //, block, options) end # Add a route condition. The route is considered non-matching when the # block returns false. def condition(name = "#{caller.first[/`.*'/]} condition", &block) @conditions << generate_method(name, &block) end def public=(value) warn ":public is no longer used to avoid overloading Module#public, use :public_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 handler_name =~/cgi/i 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) handler.run(self, server_settings) do |server| unless handler_name =~ /cgi/i $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 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) # replace with call to singleton_class once we're 1.9 only (class << self; self; end).class_eval do undef_method(name) if method_defined? name String === content ? class_eval("def #{name}() #{content}; end") : define_method(name, &content) end end # Condition for matching host name. Parameter might be String or Regexp. def host_name(pattern) condition { pattern === request.host } end # Condition for matching user agent. Parameter should be Regexp. # Will set params[:agent]. def user_agent(pattern) condition do if request.user_agent.to_s =~ pattern @params[:agent] = $~[1..-1] true else false end end end alias_method :agent, :user_agent # Condition for matching mimetypes. Accepts file extensions. def provides(*types) types.map! { |t| mime_types(t) } types.flatten! condition do if type = response['Content-Type'] types.include? type or types.include? type[/^[^;]+/] elsif type = request.preferred_type(types) params = (type.respond_to?(:params) ? type.params : {}) content_type(type, params) true else false end end end def route(verb, path, options = {}, &block) # Because of self.options.host host_name(options.delete(:host)) if options.key?(:host) enable :empty_path_info if path == "" and empty_path_info.nil? signature = compile!(verb, path, block, options) (@routes[verb] ||= []) << signature invoke_hook(:route_added, verb, path, block) signature end def invoke_hook(name, *args) extensions.each { |e| e.send(name, *args) if e.respond_to?(name) } end def generate_method(method_name, &block) method_name = method_name.to_sym define_method(method_name, &block) method = instance_method method_name remove_method method_name method end def compile!(verb, path, block, options = {}) options.each_pair { |option, args| send(option, *args) } method_name = "#{verb} #{path}" unbound_method = generate_method(method_name, &block) pattern, keys = compile path conditions, @conditions = @conditions, [] wrapper = block.arity != 0 ? proc { |a,p| unbound_method.bind(a).call(*p) } : proc { |a,p| unbound_method.bind(a).call } wrapper.instance_variable_set(:@route_name, method_name) [ pattern, keys, conditions, wrapper ] end def compile(path) if path.respond_to? :to_str keys = [] # Split the path into pieces in between forward slashes. # A negative number is given as the second argument of path.split # because with this number, the method does not ignore / at the end # and appends an empty string at the end of the return value. # segments = path.split('/', -1).map! do |segment| ignore = [] # Special character handling. # pattern = segment.to_str.gsub(/[^\?\%\\\/\:\*\w]|:(?!\w)/) do |c| ignore << escaped(c).join if c.match(/[\.@]/) patt = encoded(c) patt.gsub(/%[\da-fA-F]{2}/) do |match| match.split(//).map! { |char| char == char.downcase ? char : "[#{char}#{char.downcase}]" }.join end end ignore = ignore.uniq.join # Key handling. # pattern.gsub(/((:\w+)|\*)/) do |match| if match == "*" keys << 'splat' "(.*?)" else keys << $2[1..-1] ignore_pattern = safe_ignore(ignore) ignore_pattern end end end # Special case handling. # if last_segment = segments[-1] and last_segment.match(/\[\^\\\./) parts = last_segment.rpartition(/\[\^\\\./) parts[1] = '[^' segments[-1] = parts.join end [/\A#{segments.join('/')}\z/, keys] elsif path.respond_to?(:keys) && path.respond_to?(:match) [path, path.keys] elsif path.respond_to?(:names) && path.respond_to?(:match) [path, path.names] elsif path.respond_to? :match [path, []] else raise TypeError, path end end def encoded(char) enc = URI_INSTANCE.escape(char) enc = "(?:#{escaped(char, enc).join('|')})" if enc == char enc = "(?:#{enc}|#{encoded('+')})" if char == " " enc end def escaped(char, enc = URI_INSTANCE.escape(char)) [Regexp.escape(enc), URI_INSTANCE.escape(char, /./)] end def safe_ignore(ignore) unsafe_ignore = [] ignore = ignore.gsub(/%[\da-fA-F]{2}/) do |hex| unsafe_ignore << hex[1..2] '' end unsafe_patterns = unsafe_ignore.map! do |unsafe| chars = unsafe.split(//).map! do |char| char == char.downcase ? char : char + char.downcase end "|(?:%[^#{chars[0]}].|%[#{chars[0]}][^#{chars[1]}])" end if unsafe_patterns.length > 0 "((?:[^#{ignore}/?#%]#{unsafe_patterns.join()})+)" else "([^#{ignore}/?#]+)" end end def setup_default_middleware(builder) builder.use ExtendedRack builder.use ShowExceptions if show_exceptions? builder.use Rack::MethodOverride if method_override? builder.use Rack::Head setup_logging builder setup_sessions builder setup_protection builder end def setup_middleware(builder) middleware.each { |c,a,b| builder.use(c, *a, &b) } end def setup_logging(builder) if logging? setup_common_logger(builder) setup_custom_logger(builder) elsif logging == false setup_null_logger(builder) end end def setup_null_logger(builder) builder.use Rack::NullLogger end def setup_common_logger(builder) builder.use Sinatra::CommonLogger end def setup_custom_logger(builder) if logging.respond_to? :to_int builder.use Rack::Logger, logging else builder.use Rack::Logger end end def setup_protection(builder) return unless protection? options = Hash === protection ? protection.dup : {} protect_session = options.fetch(:session) { sessions? } options[:except] = Array options[:except] options[:except] += [:session_hijacking, :remote_token] unless protect_session options[:reaction] ||= :drop_session builder.use Rack::Protection, options end def setup_sessions(builder) return unless sessions? options = {} options[:secret] = session_secret if session_secret? options.merge! sessions.to_hash if sessions.respond_to? :to_hash builder.use Rack::Session::Cookie, options end def detect_rack_handler servers = Array(server) servers.each do |server_name| begin return Rack::Handler.get(server_name.to_s) rescue LoadError, NameError rescue ArgumentError Sinatra::Ext.get_handler(server_name.to_s) end end fail "Server handler (#{servers.join(',')}) not found." end def inherited(subclass) subclass.reset! subclass.set :app_file, caller_files.first unless subclass.app_file? super end @@mutex = Mutex.new def synchronize(&block) if lock? @@mutex.synchronize(&block) else yield end end # used for deprecation warnings def warn(message) super message + "\n\tfrom #{cleaned_caller.first.join(':')}" end # Like Kernel#caller but excluding certain magic entries def cleaned_caller(keep = 3) caller(1). map! { |line| line.split(/:(?=\d|in )/, 3)[0,keep] }. reject { |file, *_| CALLERS_TO_IGNORE.any? { |pattern| file =~ pattern } } end end # Fixes encoding issues by # * defaulting to UTF-8 # * casting params to Encoding.default_external # # The latter might not be necessary if Rack handles it one day. # Keep an eye on Rack's LH #100. def force_encoding(*args) settings.force_encoding(*args) end if defined? Encoding def self.force_encoding(data, encoding = default_encoding) return if data == settings || data.is_a?(Tempfile) if data.respond_to? :force_encoding data.force_encoding(encoding).encode! elsif data.respond_to? :each_value data.each_value { |v| force_encoding(v, encoding) } elsif data.respond_to? :each data.each { |v| force_encoding(v, encoding) } end data end else def self.force_encoding(data, *) data end end reset! set :environment, (ENV['RACK_ENV'] || :development).to_sym set :raise_errors, Proc.new { test? } set :dump_errors, Proc.new { !test? } set :show_exceptions, Proc.new { development? } set :sessions, false set :logging, false set :protection, true set :method_override, false set :use_code, false set :default_encoding, "utf-8" set :x_cascade, true set :add_charset, %w[javascript xml xhtml+xml].map { |t| "application/#{t}" } settings.add_charset << /^text\// # explicitly generating a session secret eagerly to play nice with preforking begin require 'securerandom' set :session_secret, SecureRandom.hex(64) rescue LoadError, NotImplementedError # SecureRandom raises a NotImplementedError if no random device is available set :session_secret, "%064x" % Kernel.rand(2**256-1) end class << self alias_method :methodoverride?, :method_override? alias_method :methodoverride=, :method_override= end set :run, false # start server via at-exit hook? set :running_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) ruby_engine = defined?(RUBY_ENGINE) && RUBY_ENGINE if ruby_engine == 'macruby' server.unshift 'control_tower' else server.unshift 'reel' server.unshift 'mongrel' if ruby_engine.nil? server.unshift 'puma' if ruby_engine != 'rbx' server.unshift 'thin' if ruby_engine != 'jruby' server.unshift 'puma' if ruby_engine == 'rbx' server.unshift 'trinidad' if ruby_engine == 'jruby' end set :absolute_redirects, true set :prefixed_redirects, false set :empty_path_info, nil set :app_file, nil set :root, Proc.new { app_file && File.expand_path(File.dirname(app_file)) } set :views, Proc.new { root && File.join(root, 'views') } set :reload_templates, Proc.new { development? } set :lock, false set :threaded, true set :public_folder, Proc.new { root && File.join(root, 'public') } set :static, Proc.new { public_folder && File.exist?(public_folder) } set :static_cache_control, false error ::Exception do response.status = 500 content_type 'text/html' '

Internal Server Error

' end configure :development do get '/__sinatra__/:image.png' do filename = File.dirname(__FILE__) + "/images/#{params[:image].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 :session_secret, Proc.new { super() unless development? } set :app_file, nil def self.register(*extensions, &block) #:nodoc: added_methods = extensions.map {|m| m.public_instance_methods }.flatten Delegator.delegate(*added_methods) super(*extensions, &block) end end # Sinatra delegation mixin. Mixing this module into an object causes all # methods to be delegated to the Sinatra::Application class. Used primarily # at the top-level. module Delegator #:nodoc: def self.delegate(*methods) methods.each do |method_name| define_method(method_name) do |*args, &block| return super(*args, &block) if respond_to? method_name Delegator.target.send(method_name, *args, &block) end private method_name end end delegate :get, :patch, :put, :post, :delete, :head, :options, :link, :unlink, :template, :layout, :before, :after, :error, :not_found, :configure, :set, :mime_type, :enable, :disable, :use, :development?, :test?, :production?, :helpers, :settings, :register class << self attr_accessor :target end self.target = Application end class Wrapper def initialize(stack, instance) @stack, @instance = stack, instance end def settings @instance.settings end def helpers @instance end def call(env) @stack.call(env) end def inspect "#<#{@instance.class} app_file=#{settings.app_file.inspect}>" end end # Create a new Sinatra application; the block is evaluated in the 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-1.4.8/README.fr.md0000644000004100000410000023633713044044066015121 0ustar www-datawww-data# Sinatra *Attention : Ce document correspond à la traduction de la version anglaise et il n'est peut-être plus à jour.* Sinatra est un [DSL](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 /\A\/bonjour\/([\w]+)\z/ 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 (Fixnum), en-têtes (Hash), corps de la réponse (répondant à #each)]` * Un tableau de 2 élements : `[code statut (Fixnum), corps de la réponse (répondant à #each)]` * Un objet qui répond à `#each` et qui ne transmet que des chaînes de caractères au bloc fourni * Un Fixnum représentant le code statut 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 RACK_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/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['RACK_ENV'], ou "development" si absent.
logging
utiliser le logger.
lock

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

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

method_override
utilise la magie de _method afin de permettre des formulaires put/delete dans des navigateurs qui ne le permettent pas.
port
port à écouter. Utiliser seulement pour le serveur intégré.
prefixed_redirects
si oui ou non request.script_name doit être inséré dans les redirections si un chemin non absolu est utilisé. Ainsi, redirect '/foo' se comportera comme redirect to('/foo'). Désactivé par défaut.
protection
défini s’il faut activer ou non la protection contre les attaques web. Voir la section protection précédente.
public_dir
alias pour public_folder. Voir ci-dessous.
public_folder
chemin pour le dossier à partir duquel les fichiers publics sont servis. Utilisé seulement si les fichiers statiques doivent être servis (voir le paramètre static). Si non défini, il découle du paramètre app_file.
reload_templates
si oui ou non les templates doivent être rechargés entre les requêtes. Activé en mode développement.
root
chemin pour le dossier racine du projet. Si non défini, il découle du paramètre app_file.
raise_errors
soulever les erreurs (ce qui arrêtera l’application). Désactivé par défaut sauf lorsque environment est défini à "test".
run
si activé, Sinatra s’occupera de démarrer le serveur, ne pas activer si vous utiliser rackup ou autres.
running
est-ce que le serveur intégré est en marche ? ne changez pas ce paramètre !
server
serveur ou liste de serveurs à utiliser pour le serveur intégré. Par défaut [‘thin’, ‘mongrel’, ‘webrick’], l’ordre indiquant la priorité.
sessions
active le support des sessions basées sur les cookies, en utilisant Rack::Session::Cookie. Reportez-vous à la section ‘Utiliser les sessions’ pour plus d’informations.
show_exceptions
affiche la trace de l’erreur dans le navigateur lorsqu’une exception se produit. Désactivé par défaut sauf lorsque environment est défini à "development".
static
Si oui ou non Sinatra doit s’occuper de servir les fichiers statiques. Désactivez si vous utilisez un serveur capable de le gérer lui même. Le désactiver augmentera la performance. Activé par défaut pour le style classique, désactivé pour le style modulaire.
static_cache_control
A définir quand Sinatra rend des fichiers statiques pour ajouter les en-têtes Cache-Control. Utilise le helper cache_control. Désactivé par défaut. Utiliser un array explicite pour définir des plusieurs valeurs : set :static_cache_control, [:public, :max_age => 300]
threaded
à définir à true pour indiquer à Thin d’utiliser EventMachine.defer pour traiter la requête.
views
chemin pour le dossier des vues. Si non défini, il découle du paramètre app_file.
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 `RACK_ENV`. Sa valeur par défaut est `"development"`. Dans ce mode, tous les templates sont rechargés à chaque requête. Des handlers spécifiques pour `not_found` et `error` sont installés pour vous permettre d'avoir une pile de trace dans votre navigateur. En mode `"production"` et `"test"` les templates sont mis en cache par défaut. Pour exécuter votre application dans un environnement différent, définissez la variable d'environnement `RACK_ENV` : ```shell RACK_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_rack_env get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' assert_equal "Vous utilisez Songbird !", last_response.body end end ``` ## Sinatra::Base - Les Middlewares, Bibliothèques, et Applications Modulaires Définir votre application au niveau supérieur fonctionne bien dans le cas des micro-applications mais présente pas mal d'inconvénients pour créer des composants réutilisables sous forme de middlewares Rack, de Rails metal, de simples librairies avec un composant serveur ou même d'extensions Sinatra. Le niveau supérieur suppose une configuration dans le style des micro-applications (une application d'un seul fichier, des répertoires `./public` et `./views`, des logs, une page d'erreur, etc...). C'est là que `Sinatra::Base` prend tout son intérêt : ```ruby require 'sinatra/base' class MonApplication < Sinatra::Base set :sessions, true set :foo, 'bar' get '/' do 'Bonjour le monde !' end end ``` Les méthodes de la classe `Sinatra::Base` sont parfaitement identiques à celles disponibles via le DSL de haut niveau. Il suffit de deux modifications pour transformer la plupart des applications de haut niveau en un composant `Sinatra::Base` : * Votre fichier doit charger `sinatra/base` au lieu de `sinatra`, sinon toutes les méthodes du DSL Sinatra seront importées dans l'espace de nom principal. * Les gestionnaires de routes, la gestion d'erreur, les filtres et les options doivent être placés dans une classe héritant de `Sinatra::Base`. `Sinatra::Base` est une page blanche. La plupart des options sont désactivées par défaut, y compris le serveur intégré. Reportez-vous à [Options et Configuration](http://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-1.4.8/README.pt-br.md0000644000004100000410000012515213044044066015526 0ustar www-datawww-data# 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: [http://localhost:4567](http://localhost:4567) É 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) * [Retorno de valores](#retorno-de-valores) * [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) * [Halting](#halting) * [Passing](#passing) * [Desencadeando Outra Rota](#desencadeando-outra-rota) * [Configuração](#configuração) * [Tratamento de Erros](#tratamento-de-erros) * [Erro](#erro) * [Mime Types](#mime-types) * [Rack Middleware](#rack-middleware) * [Testando](#testando) * [Sinatra::Base - Middleware, Bibliotecas e aplicativos modulares](#sinatrabase---middleware-bibliotecas-e-aplicativos-modulares) * [Linha de comando](#linha-de-comando) * [Multi-threading](#multi-threading) * [A última versão](#a-última-versão) * [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 ..pe end ``` As rotas são interpretadas na ordem em que são definidas. A primeira rota encontrada responde a requisição. 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 /\A\/ola\/([\w]+)\z/ 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), o caminho solicitado pode ser alterado antes de concluir a comparação com as suas rotas. ## Condições Rotas podem incluir uma variedade de condições, tal como o `user agent`: ```ruby get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do "Você está usando o Songbird versão #{params['agent'][0]}" end get '/foo' do # Correspondente a navegadores que não sejam Songbird end ``` Outras condições disponíveis são `host_name` e `provides`: ```ruby get '/', :host_name => /^admin\./ do "Área administrativa. Acesso negado!" end get '/', :provides => 'html' do haml :index end get '/', :provides => ['rss', 'atom', 'xml'] do builder :feed end ``` `provides` procura pelos Accept header 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 (Fixnum), cabecalho (Hash), corpo da resposta (responde à #each)]` * Um array com dois elementros: `[status (Fixnum), corpo da resposta (responde à #each)]` * Um objeto que responda à `#each` sem passar nada, mas, sim, `strings` para um dado bloco * Um objeto `Fixnum` representando o código de status Dessa forma, podemos implementar facilmente um exemplo de streaming: ```ruby class Stream def each 100.times { |i| yield "#{i}\n" } end end get('/') { Stream.new } ``` Você também pode usar o método auxiliar `stream` (descrito abaixo) para incorporar a lógica de streaming na rota. ## 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`. ## Views / Templates Cada linguagem de template é exposta através de seu próprio método de renderização. Estes metodos 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 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 instancia da aplicação. Se você mudar isto as variáveis de instânciae metodos 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 direto 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. ### 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 simplesmente requere-la primeiro: ```ruby require 'rdiscount' # ou require 'bluecloth' get('/') { markdown :index } ``` #### Haml Templates
Dependencia haml
Extencao do Arquivo .haml
Exemplo haml :index, :format => :html5
#### Erb Templates
Dependencia erubis or erb (included in Ruby)
Extencao do Arquivos .erb, .rhtml or .erubis (Erubis only)
Exemplo erb :index
#### Builder Templates
Dependencia builder
Extencao do Arquivo .builder
Exemplo builder { |xml| xml.em "hi" }
It also takes a block for inline templates (see exemplo). #### Nokogiri Templates
Dependencia nokogiri
Extencao do Arquivo .nokogiri
Exemplo nokogiri { |xml| xml.em "hi" }
It also takes a block for inline templates (see exemplo). #### Sass Templates
Dependencia sass
Extencao do Arquivo .sass
Exemplo sass :stylesheet, :style => :expanded
#### SCSS Templates
Dependencia sass
Extencao do Arquivo .scss
Exemplo scss :stylesheet, :style => :expanded
#### Less Templates
Dependencia less
Extencao do Arquivo .less
Exemplo less :stylesheet
#### Liquid Templates
Dependencia liquid
Extencao 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
Dependencia Anyone of: RDiscount, RedCarpet, BlueCloth, kramdown, maruku
Extencao 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.
Dependencia RedCloth
Extencao 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
Dependencia RDoc
Extencao 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
Dependencia Asciidoctor
Extencao 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
Dependencia Radius
Extencao 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
Dependencia Markaby
Extencao do Arquivo .mab
Exemplo markaby { h1 "Welcome!" }
Este também recebe um bloco para templates (veja o exemplo). #### RABL Templates
Dependencia Rabl
Extencao do Arquivo .rabl
Exemplo rabl :index
#### Slim Templates
Dependencia Slim Lang
Extencao do Arquivo .slim
Exemplo slim :index
#### Creole Templates
Dependencia Creole
Extencao 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 vcoê 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
Dependencia WikiCloth
Extencao 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
Dependencia CoffeeScript and a way to execute javascript
Extencao do Arquivo .coffee
Exemplo coffee :index
#### Stylus Templates
Dependencia Stylus and a way to execute javascript
Extencao 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
Dependencia yajl-ruby
Extencao 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
Dependencia WLang
Extencao 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 setadas em rotas manipuladas são diretamente acessadas por templates: ```ruby get '/:id' do @foo = Foo.find(params['id']) haml '%h1= @foo.nome' end ``` Ou, especifique um hash explícito para variáveis locais: ```ruby get '/:id' do foo = Foo.find(params['id']) haml '%h1= foo.nome', :locals => { :foo => foo } end ``` Isso é tipicamente utilizando quando renderizamos templates como partials dentro de outros templates. ### Templates com `yield` e layouts aninhados O 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 também o método `render`. ### Templates Inline Templates podem ser definidos no final do arquivo fonte(.rb): ```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\` se você tem templates inline no outro arquivo fonte. ### Templates nomeados Templates também podem ser definidos utilizando o método top-level `template`: ```ruby template :layout do "%html\n =yield\n" end template :index do '%div.title Olá Mundo!' end get '/' do haml :index end ``` Se existir um template com nome “layout”, ele será utilizado toda vez que um template for renderizado. Você pode desabilitar layouts passando `:layout => false`. ```ruby get '/' do haml :index, :layout => !request.xhr? end ``` ### 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`. Veja https://github.com/rtomayko/tilt para saber mais sobre Tilt. ### 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 setadas nos filtros são acessadas através de rotas e templates: ```ruby before do @nota = 'Oi!' request.path_info = '/foo/bar/baz' end get '/foo/*' do @nota #=> 'Oi!' params['splat'] #=> 'bar/baz' end ``` Filtros After são avaliados após cada requisição dentro do contexto da requisição e também podem modificar a requisição e a resposta. Variáveis de instância e rotas definidas nos filtros before são acessadas 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 authenticate! end after '/create/:slug' do |slug| session[:last_slug] = slug 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 ``` ### Utilizando Sessões Sessões são usadas para manter um estado durante uma requisição. Se ativa, 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 ``` Note que `enable :sessions` utilizará um cookie para guardar todos os dados da sessão. Isso nem sempre pode ser o que você quer (guardar muitos dados irá aumentar o seu tráfego, por exemplo). Você pode utilizar qualquer Rack middleware de sessão: para fazer isso **não** utilize o método `enable :sessions`, ao invés disso utilize seu middleware de sessão como utilizaria qualquer outro: ```ruby use Rack::Session::Pool, :expire_after => 2592000 get '/' do "value = " << session[:value].inspect end get '/:value' do session['value'] = params['value'] end ``` Para melhorar a segurança, os dados da sessão guardados no cookie é assinado com uma chave secreta da sessão. Uma chave aleatória é gerada para você pelo Sinatra. Contudo, já que a chave mudará cada vez que você inicia sua aplicação, você talvez queira defini-la você mesmo para que todas as instâncias da aplicação compartilhe-a: ```ruby set :session_secret, 'super secret' ``` Se você quiser fazer outras configurações, você também pode guardar um hash com as opções nas configurações da `session`: ```ruby set :sessions, :domain => 'foo.com' ``` Para compartilhar sua sessão entre outros aplicativos em um subdomínio de foo.com, utilize o prefixo *.*: ```ruby set :sessions, :domain => '.foo.com' ``` ### Halting Para parar imediatamente uma requisição com um filtro ou rota utilize: ```ruby halt ``` Você também pode especificar o status quando parar… ```ruby halt 410 ``` Ou com corpo de texto… ```ruby halt 'isso será o corpo do texto' ``` Ou também… ```ruby halt 401, 'vamos embora!' ``` Com cabeçalhos… ```ruby halt 402, {'Content-Type' => 'text/plain'}, 'revanche' ``` É obviamente possivel 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 de parâmetro. Se o parâmetro da rota não for encontrado, 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. 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 ao simplemente mover o `"bar"` em um helper usado por ambos `/foo` e `/bar`. Se você quer que a requisição seja enviada para a mesma instancia da aplicação ao invês de uma duplicada, use `call!` no lugar de `call`. Veja a especificação do Rack se você quer aprender mais sobre o `call`. ## Configuração Rodando uma vez, na inicialização, em qualquer ambiente: ```ruby configure do ... end ``` Rodando somente quando o ambiente (`RACK_ENV` environment variável) é setado para `:production`: ```ruby configure :production do ... end ``` Rodando quando o ambiente é setado para `:production` ou `:test`: ```ruby configure :production, :test do ... end ``` ## Tratamento de Erros Tratamento de erros rodam dentro do mesmo contexto como rotas e filtros before, o que significa que você pega todos os presentes que tem para oferecer, como `haml`, `erb`, `halt`, etc. ### Não Encontrado Quando um `Sinatra::NotFound` exception é levantado, ou o código de status da reposta é 404, o 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. 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 manipulador de erro para um código de status: ```ruby error 403 do 'Accesso negado' end get '/secreto' do 403 end ``` Ou um range: ```ruby error 400..510 do 'Boom' end ``` O Sinatra instala os manipuladores especiais `not_found` e `error` quando roda sobre o ambiente de desenvolvimento. ## Mime Types Quando utilizamos `send_file` ou arquivos estáticos você pode ter mime types Sinatra não entendidos. Use `mime_type` para registrar eles por extensão de arquivos: ```ruby mime_type :foo, 'text/foo' ``` Você também pode utilizar isto com o helper `content_type`: ```ruby content_type :foo ``` ## 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. ## 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 em um nível superior de trabalho funciona bem para micro aplicativos, mas tem consideráveis incovenientes na construção de componentes reutilizáveis como um middleware Rack, metal Rails, bibliotecas simples como um componente de servidor, ou mesmo extensões Sinatra. A DSL de nível superior polui o espaço do objeto e assume um estilo de configuração de micro aplicativos (exemplo: uma simples arquivo de aplicação, diretórios `./public` e `./views`, logs, página de detalhes de exceção, etc.). É onde o `Sinatra::Base` entra em jogo: ```ruby require 'sinatra/base' class MinhaApp < Sinatra::Base set :sessions, true set :foo, 'bar' get '/' do 'Ola mundo!' end end ``` A classe `MinhaApp` é um componente Rack independente que pode agir como um middleware Rack, uma aplicação Rack, ou metal Rails. Você pode utilizar ou executar esta classe com um arquivo rackup `config.ru`; ou, controlar um componente de servidor fornecendo como biblioteca: ```ruby MinhaApp.run! :host => 'localhost', :port => 9090 ``` Os métodos disponíveis para subclasses `Sinatra::Base` são exatamente como aqueles disponíveis via a DSL de nível superior. Aplicações de nível mais alto podem ser convertidas para componentes `Sinatra::Base` com duas modificações: - Seu arquivo deve requerer `sinatra/base` ao invés de `sinatra`; outra coisa, todos os métodos DSL do Sinatra são importados para o espaço principal. - Coloque as rotas da sua aplicação, manipuladores de erro, filtros e opções na subclasse de um `Sinatra::Base`. `Sinatra::Base` é um quadro branco. Muitas opções são desabilitadas por padrão, incluindo o servidor embutido. Veja [Opções e Configurações](http://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ê uma olhada no código você mesmo: aqui está o [Sinatra::Delegator mixin](http://github.com/sinatra/sinatra/blob/ceac46f0bc129a6e994a06100aa854f606fe5992/lib/sinatra/base.rb#L1128) sendo [incluido dentro de um espaço principal](http://github.com/sinatra/sinatra/blob/ceac46f0bc129a6e994a06100aa854f606fe5992/lib/sinatra/main.rb#L28) ## Linha de Comando Aplicações Sinatra podem ser executadas diretamente: ```shell ruby minhaapp.rb [-h] [-x] [-e AMBIENTE] [-p PORTA] [-o HOST] [-s SERVIDOR] ``` As opções são: ``` -h # ajuda -p # define a porta (padrão é 4567) -o # define o host (padrão é 0.0.0.0) -e # define o ambiente (padrão é development) -s # especifica o servidor/manipulador rack (padrão é thin) -x # ativa o bloqueio (padrão é desligado) ``` ### 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 ``` [resposrta-so]: http://stackoverflow.com/questions/6278817/is-sinatra-multi-threaded/6282999#6282999) ## A última versão Se você gostaria de utilizar o código da última versão do Sinatra, crie um clone local e execute sua aplicação com o diretório `sinatra/lib` no `LOAD_PATH`: ```shell cd minhaapp git clone git://github.com/sinatra/sinatra.git ruby -I sinatra/lib minhaapp.rb ``` Alternativamente, você pode adicionar o diretório do `sinatra/lib` no `LOAD_PATH` do seu aplicativo: ```ruby $LOAD_PATH.unshift File.dirname(__FILE__) + '/sinatra/lib' require 'rubygems' require 'sinatra' get '/sobre' do "Estou rodando a versão" + Sinatra::VERSION end ``` Para atualizar o código do Sinatra no futuro: ```shell cd meuprojeto/sinatra git pull ``` ## Mais * [Website do Projeto](http://www.sinatrarb.com/) - Documentação adicional, novidades e links para outros recursos. * [Contribuir](http://www.sinatrarb.com/contributing) - Encontrar um bug? Precisa de ajuda? Tem um patch? * [Acompanhar Questões](https://github.com/sinatra/sinatra/issues) * [Twitter](https://twitter.com/sinatra) * [Lista de Email](http://groups.google.com/group/sinatrarb/topics) * [Sinatra Book](https://github.com/sinatra/sinatra-book/) Livro de Receitas * Documentação da API para a [última release](http://www.rubydoc.info/gems/sinatra) * [IRC: \#sinatra](irc://chat.freenode.net/#sinatra) em [freenode.net](http://freenode.net) * [Servidor de CI](https://travis-ci.org/sinatra/sinatra) sinatra-1.4.8/test/0000755000004100000410000000000013044044066014175 5ustar www-datawww-datasinatra-1.4.8/test/views/0000755000004100000410000000000013044044066015332 5ustar www-datawww-datasinatra-1.4.8/test/views/hello.less0000644000004100000410000000010113044044066017315 0ustar www-datawww-data@white_colour: #fff; #main { background-color: @white_colour; }sinatra-1.4.8/test/views/layout2.erb0000644000004100000410000000003113044044066017415 0ustar www-datawww-dataERB Layout! <%= yield %> sinatra-1.4.8/test/views/hello.slim0000644000004100000410000000002313044044066017316 0ustar www-datawww-datah1 Hello From Slim sinatra-1.4.8/test/views/hello.coffee0000644000004100000410000000001513044044066017602 0ustar www-datawww-dataalert "Aye!" sinatra-1.4.8/test/views/b/0000755000004100000410000000000013044044066015553 5ustar www-datawww-datasinatra-1.4.8/test/views/b/in_b.str0000644000004100000410000000001313044044066017206 0ustar www-datawww-dataGimme a B! sinatra-1.4.8/test/views/hello.str0000644000004100000410000000003213044044066017162 0ustar www-datawww-data

Hello From String

sinatra-1.4.8/test/views/layout2.haml0000644000004100000410000000003313044044066017570 0ustar www-datawww-data%h1 HAML Layout! %p= yield sinatra-1.4.8/test/views/hello.creole0000644000004100000410000000002413044044066017624 0ustar www-datawww-data= Hello From Creole sinatra-1.4.8/test/views/explicitly_nested.str0000644000004100000410000000007613044044066021617 0ustar www-datawww-data#{render :str, :hello, :layout => :layout2}sinatra-1.4.8/test/views/hello.liquid0000644000004100000410000000003313044044066017642 0ustar www-datawww-data

Hello From Liquid

sinatra-1.4.8/test/views/layout2.mab0000644000004100000410000000004113044044066017405 0ustar www-datawww-datah1 "Markaby Layout!" p { yield } sinatra-1.4.8/test/views/hello.scss0000644000004100000410000000004313044044066017327 0ustar www-datawww-data#scss { background-color: white }sinatra-1.4.8/test/views/hello.radius0000644000004100000410000000003313044044066017642 0ustar www-datawww-data

Hello From Radius

sinatra-1.4.8/test/views/layout2.liquid0000644000004100000410000000005313044044066020140 0ustar www-datawww-data

Liquid Layout!

{{ yield }}

sinatra-1.4.8/test/views/error.haml0000644000004100000410000000017213044044066017326 0ustar www-datawww-data%h1 Hello From Haml = raise 'goodbye' unless defined?(french) && french = raise 'au revoir' if defined?(french) && french sinatra-1.4.8/test/views/layout2.test0000644000004100000410000000001213044044066017623 0ustar www-datawww-dataLayout 2! sinatra-1.4.8/test/views/layout2.builder0000644000004100000410000000004113044044066020274 0ustar www-datawww-dataxml.layout do xml << yield end sinatra-1.4.8/test/views/error.erb0000644000004100000410000000020313044044066017150 0ustar www-datawww-dataHello <%= 'World' %> <% raise 'Goodbye' unless defined?(french) && french %> <% raise 'Au revoir' if defined?(french) && french %> sinatra-1.4.8/test/views/utf8.erb0000644000004100000410000000007713044044066016716 0ustar www-datawww-data

<%= value %>

Ingen vill veta var du köpt din tröja. sinatra-1.4.8/test/views/nested.str0000644000004100000410000000005113044044066017342 0ustar www-datawww-data#{render :str, :hello}sinatra-1.4.8/test/views/hello.md0000644000004100000410000000002513044044066016754 0ustar www-datawww-data# Hello From Markdownsinatra-1.4.8/test/views/layout2.wlang0000644000004100000410000000002713044044066017762 0ustar www-datawww-dataWLang Layout! +{yield} sinatra-1.4.8/test/views/error.builder0000644000004100000410000000004313044044066020030 0ustar www-datawww-dataxml.error do raise "goodbye" end sinatra-1.4.8/test/views/hello.wlang0000644000004100000410000000002213044044066017461 0ustar www-datawww-dataHello from wlang! sinatra-1.4.8/test/views/hello.rdoc0000644000004100000410000000002213044044066017300 0ustar www-datawww-data= Hello From RDoc sinatra-1.4.8/test/views/hello.test0000644000004100000410000000001513044044066017332 0ustar www-datawww-dataHello World! sinatra-1.4.8/test/views/layout2.radius0000644000004100000410000000005313044044066020140 0ustar www-datawww-data

Radius Layout!

sinatra-1.4.8/test/views/hello.sass0000644000004100000410000000004013044044066017322 0ustar www-datawww-data#sass :background-color white sinatra-1.4.8/test/views/layout2.str0000644000004100000410000000004013044044066017455 0ustar www-datawww-data

String Layout!

#{yield}sinatra-1.4.8/test/views/layout2.rabl0000644000004100000410000000005013044044066017566 0ustar www-datawww-datanode(:qux) do ::JSON.parse(yield) end sinatra-1.4.8/test/views/hello.rabl0000644000004100000410000000003413044044066017274 0ustar www-datawww-dataobject @foo attributes :bar sinatra-1.4.8/test/views/hello.builder0000644000004100000410000000004713044044066020006 0ustar www-datawww-dataxml.exclaim "You're my boy, #{@name}!" sinatra-1.4.8/test/views/hello.styl0000644000004100000410000000002013044044066017342 0ustar www-datawww-dataa margin auto sinatra-1.4.8/test/views/hello.mab0000644000004100000410000000003013044044066017107 0ustar www-datawww-datah1 "Hello From Markaby" sinatra-1.4.8/test/views/hello.asciidoc0000644000004100000410000000002713044044066020134 0ustar www-datawww-data== Hello from AsciiDoc sinatra-1.4.8/test/views/hello.mediawiki0000644000004100000410000000003113044044066020314 0ustar www-datawww-data''Hello from MediaWiki'' sinatra-1.4.8/test/views/foo/0000755000004100000410000000000013044044066016115 5ustar www-datawww-datasinatra-1.4.8/test/views/foo/hello.test0000644000004100000410000000003513044044066020117 0ustar www-datawww-datafrom another views directory sinatra-1.4.8/test/views/calc.html.erb0000644000004100000410000000001413044044066017664 0ustar www-datawww-data<%= 1 + 1 %>sinatra-1.4.8/test/views/hello.yajl0000644000004100000410000000003313044044066017312 0ustar www-datawww-datajson = { :yajl => "hello" }sinatra-1.4.8/test/views/hello.erb0000644000004100000410000000002513044044066017124 0ustar www-datawww-dataHello <%= 'World' %> sinatra-1.4.8/test/views/error.sass0000644000004100000410000000002613044044066017354 0ustar www-datawww-data#sass +argle-bargle sinatra-1.4.8/test/views/hello.textile0000644000004100000410000000002713044044066020034 0ustar www-datawww-datah1. Hello From Textile sinatra-1.4.8/test/views/hello.nokogiri0000644000004100000410000000004713044044066020201 0ustar www-datawww-dataxml.exclaim "You're my boy, #{@name}!" sinatra-1.4.8/test/views/layout2.slim0000644000004100000410000000003513044044066017615 0ustar www-datawww-datah1 Slim Layout! p == yield sinatra-1.4.8/test/views/a/0000755000004100000410000000000013044044066015552 5ustar www-datawww-datasinatra-1.4.8/test/views/a/in_a.str0000644000004100000410000000001413044044066017205 0ustar www-datawww-dataGimme an A! sinatra-1.4.8/test/views/ascii.erb0000644000004100000410000000005513044044066017114 0ustar www-datawww-dataThis file has no unicode in it! <%= value %> sinatra-1.4.8/test/views/hello.haml0000644000004100000410000000002413044044066017274 0ustar www-datawww-data%h1 Hello From Haml sinatra-1.4.8/test/views/layout2.nokogiri0000644000004100000410000000004113044044066020467 0ustar www-datawww-dataxml.layout do xml << yield end sinatra-1.4.8/test/helper.rb0000644000004100000410000000576613044044066016017 0ustar www-datawww-dataENV['RACK_ENV'] = 'test' Encoding.default_external = "UTF-8" if defined? Encoding RUBY_ENGINE = 'ruby' unless defined? RUBY_ENGINE begin require 'rack' rescue LoadError require 'rubygems' require 'rack' end testdir = File.dirname(__FILE__) $LOAD_PATH.unshift testdir unless $LOAD_PATH.include?(testdir) libdir = File.dirname(File.dirname(__FILE__)) + '/lib' $LOAD_PATH.unshift libdir unless $LOAD_PATH.include?(libdir) require 'minitest' require 'contest' require 'rack/test' 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| p 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-1.4.8/test/coffee_test.rb0000644000004100000410000000440713044044066017015 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) begin require 'coffee-script' require 'execjs' begin ExecJS.compile '1' rescue Exception raise LoadError, 'unable to execute JavaScript' end class CoffeeTest < 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 "#{$!.to_s}: skipping coffee tests" rescue if $!.class.name == 'ExecJS::RuntimeUnavailable' warn "#{$!.to_s}: skipping coffee tests" else raise end end sinatra-1.4.8/test/server_test.rb0000644000004100000410000000211313044044066017064 0ustar www-datawww-datarequire 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 end sinatra-1.4.8/test/route_added_hook_test.rb0000644000004100000410000000251413044044066021062 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) module RouteAddedTest @routes, @procs = [], [] def self.routes ; @routes ; end def self.procs ; @procs ; end def self.route_added(verb, path, proc) @routes << [verb, path] @procs << proc end end class RouteAddedHookTest < 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-1.4.8/test/rabl_test.rb0000644000004100000410000000370713044044066016510 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) begin require 'rabl' require 'ostruct' require 'json' require 'active_support/core_ext/hash/conversions' class RablTest < 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 "#{$!.to_s}: skipping rabl tests" end sinatra-1.4.8/test/routing_test.rb0000644000004100000410000010213513044044066017252 0ustar www-datawww-data# I like coding: UTF-8 require File.expand_path('../helper', __FILE__) # Helper method for easy route pattern matching testing def route_def(pattern) mock_app { get(pattern) { } } end class RegexpLookAlike class MatchData def captures ["this", "is", "a", "test"] end end def match(string) ::RegexpLookAlike::MatchData.new if string == "/this/is/a/test/" end def keys ["one", "two", "three", "four"] end end class RoutingTest < 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 "404s when no route satisfies the request" do mock_app { get('/foo') { } } get '/bar' assert_equal 404, status end it "404s and sets X-Cascade header when no route satisfies the request" do mock_app { get('/foo') { } } get '/bar' assert_equal 404, status assert_equal 'pass', response.headers['X-Cascade'] end it "404s and does not set X-Cascade header when no route satisfies the request and x_cascade has been disabled" do mock_app { disable :x_cascade get('/foo') { } } get '/bar' assert_equal 404, status assert_equal nil, response.headers['X-Cascade'] end it "allows using unicode" do mock_app do get('/föö') { } end get '/f%C3%B6%C3%B6' assert_equal 200, status end it "it handles encoded slashes correctly" do mock_app { 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 'matches empty PATH_INFO to "/" if no route is defined for ""' do mock_app do get '/' do 'worked' end end get '/', {}, "PATH_INFO" => "" assert ok? assert_equal 'worked', body end it 'matches empty PATH_INFO to "" if a route is defined for ""' do mock_app do disable :protection get '/' do 'did not work' end get '' do 'worked' end end get '/', {}, "PATH_INFO" => "" assert ok? assert_equal 'worked', body end it 'takes multiple definitions of a route' do mock_app { user_agent(/Foo/) get '/foo' do 'foo' end get '/foo' do 'not foo' end } get '/foo', {}, 'HTTP_USER_AGENT' => 'Foo' assert ok? assert_equal 'foo', body get '/foo' assert ok? assert_equal 'not foo', body end it "exposes params with indifferent hash" do mock_app { get '/:foo' do assert_equal 'bar', params['foo'] assert_equal 'bar', params[:foo] 'well, alright' end } get '/bar' assert_equal 'well, alright', body end it "merges named params and query string params in params" do mock_app { get '/:foo' do assert_equal 'bar', params['foo'] assert_equal 'biz', params['baz'] end } get '/bar?baz=biz' assert ok? end it "supports named params like /hello/:person" do mock_app { get '/hello/:person' do "Hello #{params['person']}" end } get '/hello/Frank' assert_equal 'Hello Frank', body end it "supports optional named params like /?:foo?/?:bar?" do mock_app { get '/?:foo?/?:bar?' do "foo=#{params[:foo]};bar=#{params[:bar]}" end } get '/hello/world' assert ok? assert_equal "foo=hello;bar=world", body get '/hello' assert ok? assert_equal "foo=hello;bar=", body get '/' assert ok? assert_equal "foo=;bar=", body end it "supports named captures like %r{/hello/(?[^/?#]+)} on Ruby >= 1.9" do next if RUBY_VERSION < '1.9' mock_app { get Regexp.new('/hello/(?[^/?#]+)') do "Hello #{params['person']}" end } get '/hello/Frank' assert_equal 'Hello Frank', body end it "supports optional named captures like %r{/page(?.[^/?#]+)?} on Ruby >= 1.9" do next if RUBY_VERSION < '1.9' mock_app { get Regexp.new('/page(?.[^/?#]+)?') do "format=#{params[:format]}" end } get '/page.html' assert ok? assert_equal "format=.html", body get '/page.xml' assert ok? assert_equal "format=.xml", body get '/page' assert ok? assert_equal "format=", body end it 'does not 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" do route_def '/test(bar)/' get '/test(bar)/' assert ok? end it "supports basic nested params" do mock_app { get '/hi' do params["person"]["name"] end } get "/hi?person[name]=John+Doe" assert ok? assert_equal "John Doe", body end it "exposes nested params with indifferent hash" do mock_app { get '/testme' do assert_equal 'baz', params['bar']['foo'] assert_equal 'baz', params['bar'][:foo] 'well, alright' end } get '/testme?bar[foo]=baz' assert_equal 'well, alright', body end it "exposes params nested within arrays with indifferent hash" do mock_app { get '/testme' do assert_equal 'baz', params['bar'][0]['foo'] assert_equal 'baz', params['bar'][0][:foo] 'well, alright' end } get '/testme?bar[][foo]=baz' assert_equal 'well, alright', body end it "supports arrays within params" do mock_app { get '/foo' do assert_equal ['A', 'B'], params['bar'] 'looks good' end } get '/foo?bar[]=A&bar[]=B' assert ok? assert_equal 'looks good', body end it "supports deeply nested params" do expected_params = { "emacs" => { "map" => { "goto-line" => "M-g g" }, "version" => "22.3.1" }, "browser" => { "firefox" => {"engine" => {"name"=>"spidermonkey", "version"=>"1.7.0"}}, "chrome" => {"engine" => {"name"=>"V8", "version"=>"1.0"}} }, "paste" => {"name"=>"hello world", "syntax"=>"ruby"} } mock_app { get '/foo' do assert_equal expected_params, params 'looks good' end } get '/foo', expected_params assert ok? assert_equal 'looks good', body end it "preserves non-nested params" do mock_app { get '/foo' do assert_equal "2", params["article_id"] assert_equal "awesome", params['comment']['body'] assert_nil params['comment[body]'] 'looks good' end } get '/foo?article_id=2&comment[body]=awesome' assert ok? assert_equal 'looks good', body end it "matches paths that include spaces encoded with %20" do mock_app { get '/path with spaces' do 'looks good' end } get '/path%20with%20spaces' assert ok? assert_equal 'looks good', body end it "matches paths that include spaces encoded with +" do mock_app { get '/path with spaces' do 'looks good' end } get '/path+with+spaces' assert ok? assert_equal 'looks good', body end it "matches paths that include ampersands" do mock_app { get '/:name' do 'looks good' end } get '/foo&bar' assert ok? assert_equal 'looks good', body end it "URL decodes named parameters and splats" do mock_app { get '/:foo/*' do assert_equal 'hello world', params['foo'] assert_equal ['how are you'], params['splat'] nil end } get '/hello%20world/how%20are%20you' assert ok? end it 'supports regular expressions' do mock_app { get(/^\/foo...\/bar$/) do 'Hello World' end } get '/foooom/bar' assert ok? assert_equal 'Hello World', body end it 'makes regular expression captures available in params[:captures]' do mock_app { get(/^\/fo(.*)\/ba(.*)/) do assert_equal ['orooomma', 'f'], params[:captures] 'right on' end } get '/foorooomma/baf' assert ok? assert_equal 'right on', body end it 'supports regular expression look-alike routes' do mock_app { get(RegexpLookAlike.new) do assert_equal 'this', params[:one] assert_equal 'is', params[:two] assert_equal 'a', params[:three] assert_equal 'test', params[:four] 'right on' end } get '/this/is/a/test/' assert ok? assert_equal 'right on', body end it 'raises a TypeError when pattern is not a String or Regexp' do assert_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 "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 '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 # NOTE Block params behaves differently under 1.8 and 1.9. Under 1.8, block # param arity is lax: declaring a mismatched number of block params results # in a warning. Under 1.9, block param arity is strict: mismatched block # arity raises an ArgumentError. if RUBY_VERSION >= '1.9' it 'raises an ArgumentError with block param arity 1 and no values' do mock_app { get '/foo' do |foo| 'quux' end } assert_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 else it 'does not raise an ArgumentError with block param arity 1 and no values' do mock_app { get '/foo' do |foo| 'quux' end } silence_warnings { get '/foo' } assert ok? assert_equal 'quux', body end it 'does not raise an ArgumentError with block param arity 1 and too many values' do mock_app { get '/:foo/:bar/:baz' do |foo| 'quux' end } silence_warnings { get '/a/b/c' } assert ok? assert_equal 'quux', body end end it "matches routes defined in superclasses" do base = Class.new(Sinatra::Base) base.get('/foo') { 'foo in baseclass' } mock_app(base) { get('/bar') { 'bar in subclass' } } get '/foo' assert ok? assert_equal 'foo in baseclass', body get '/bar' assert ok? assert_equal 'bar in subclass', body end it "matches routes in subclasses before superclasses" do base = Class.new(Sinatra::Base) base.get('/foo') { 'foo in baseclass' } base.get('/bar') { 'bar in baseclass' } mock_app(base) { get('/foo') { 'foo in subclass' } } get '/foo' assert ok? assert_equal 'foo in subclass', body get '/bar' assert ok? assert_equal 'bar in baseclass', body end it "adds hostname condition when it is in options" do mock_app { get '/foo', :host => 'host' do 'foo' end } get '/foo' assert not_found? end it 'allows using call to fire another request internally' do mock_app do get '/foo' do status, headers, body = call env.merge("PATH_INFO" => '/bar') [status, headers, body.each.map(&:upcase)] end get '/bar' do "bar" end end get '/foo' assert ok? assert_body "BAR" end it 'plays well with other routing middleware' do middleware = Sinatra.new inner_app = Sinatra.new { get('/foo') { 'hello' } } builder = Rack::Builder.new do use middleware map('/test') { run inner_app } end @app = builder.to_app get '/test/foo' assert ok? assert_body 'hello' end it 'returns the route signature' do signature = list = nil mock_app do signature = post('/') { } list = routes['POST'] end assert_equal Array, signature.class assert_equal 4, signature.length assert list.include?(signature) end it "sets env['sinatra.route'] to the matched route" do mock_app do after do assert_equal 'GET /users/:id/status', env['sinatra.route'] end get('/users/:id/status') { 'ok' } end get '/users/1/status' end end sinatra-1.4.8/test/compile_test.rb0000644000004100000410000002002213044044066017205 0ustar www-datawww-data# I like coding: UTF-8 require File.expand_path('../helper', __FILE__) class CompileTest < Minitest::Test def self.converts pattern, expected_regexp it "generates #{expected_regexp.source} from #{pattern}" do compiled, _ = compiled pattern assert_equal expected_regexp, compiled, "Pattern #{pattern} is not compiled into #{expected_regexp.source}. Was #{compiled.source}." end end def self.parses pattern, example, expected_params it "parses #{example} with #{pattern} into params #{expected_params}" do compiled, keys = compiled pattern match = compiled.match(example) fail %Q{"#{example}" does not parse on pattern "#{pattern}" (compiled pattern is #{compiled.source}).} unless match # Aggregate e.g. multiple splat values into one array. # params = keys.zip(match.captures).reduce({}) do |hash, mapping| key, value = mapping hash[key] = if existing = hash[key] existing.respond_to?(:to_ary) ? existing << value : [existing, value] else value end hash end assert_equal expected_params, params, "Pattern #{pattern} does not match path #{example}." end end def self.fails pattern, example it "does not parse #{example} with #{pattern}" do compiled, _ = compiled pattern match = compiled.match(example) fail %Q{"#{pattern}" does parse "#{example}" but it should fail} if match end end def compiled pattern app ||= mock_app {} compiled, keys = app.send(:compile, pattern) [compiled, keys] end converts "/", %r{\A/\z} parses "/", "/", {} converts "/foo", %r{\A/foo\z} parses "/foo", "/foo", {} converts "/:foo", %r{\A/([^/?#]+)\z} parses "/:foo", "/foo", "foo" => "foo" parses "/:foo", "/foo.bar", "foo" => "foo.bar" parses "/:foo", "/foo%2Fbar", "foo" => "foo%2Fbar" parses "/:foo", "/%0Afoo", "foo" => "%0Afoo" fails "/:foo", "/foo?" fails "/:foo", "/foo/bar" fails "/:foo", "/" fails "/:foo", "/foo/" converts "/föö", %r{\A/f%[Cc]3%[Bb]6%[Cc]3%[Bb]6\z} parses "/föö", "/f%C3%B6%C3%B6", {} converts "/:foo/:bar", %r{\A/([^/?#]+)/([^/?#]+)\z} parses "/:foo/:bar", "/foo/bar", "foo" => "foo", "bar" => "bar" converts "/hello/:person", %r{\A/hello/([^/?#]+)\z} parses "/hello/:person", "/hello/Frank", "person" => "Frank" converts "/?:foo?/?:bar?", %r{\A/?([^/?#]+)?/?([^/?#]+)?\z} parses "/?:foo?/?:bar?", "/hello/world", "foo" => "hello", "bar" => "world" parses "/?:foo?/?:bar?", "/hello", "foo" => "hello", "bar" => nil parses "/?:foo?/?:bar?", "/", "foo" => nil, "bar" => nil parses "/?:foo?/?:bar?", "", "foo" => nil, "bar" => nil converts "/*", %r{\A/(.*?)\z} parses "/*", "/", "splat" => "" parses "/*", "/foo", "splat" => "foo" parses "/*", "/foo/bar", "splat" => "foo/bar" converts "/:foo/*", %r{\A/([^/?#]+)/(.*?)\z} parses "/:foo/*", "/foo/bar/baz", "foo" => "foo", "splat" => "bar/baz" converts "/:foo/:bar", %r{\A/([^/?#]+)/([^/?#]+)\z} parses "/:foo/:bar", "/user@example.com/name", "foo" => "user@example.com", "bar" => "name" converts "/test$/", %r{\A/test(?:\$|%24)/\z} parses "/test$/", "/test$/", {} converts "/te+st/", %r{\A/te(?:\+|%2[Bb])st/\z} parses "/te+st/", "/te+st/", {} fails "/te+st/", "/test/" fails "/te+st/", "/teeest/" converts "/test(bar)/", %r{\A/test(?:\(|%28)bar(?:\)|%29)/\z} parses "/test(bar)/", "/test(bar)/", {} converts "/path with spaces", %r{\A/path(?:%20|(?:\+|%2[Bb]))with(?:%20|(?:\+|%2[Bb]))spaces\z} parses "/path with spaces", "/path%20with%20spaces", {} parses "/path with spaces", "/path%2Bwith%2Bspaces", {} parses "/path with spaces", "/path+with+spaces", {} converts "/foo&bar", %r{\A/foo(?:&|%26)bar\z} parses "/foo&bar", "/foo&bar", {} converts "/:foo/*", %r{\A/([^/?#]+)/(.*?)\z} parses "/:foo/*", "/hello%20world/how%20are%20you", "foo" => "hello%20world", "splat" => "how%20are%20you" converts "/*/foo/*/*", %r{\A/(.*?)/foo/(.*?)/(.*?)\z} parses "/*/foo/*/*", "/bar/foo/bling/baz/boom", "splat" => ["bar", "bling", "baz/boom"] fails "/*/foo/*/*", "/bar/foo/baz" converts "/test.bar", %r{\A/test(?:\.|%2[Ee])bar\z} parses "/test.bar", "/test.bar", {} fails "/test.bar", "/test0bar" converts "/:file.:ext", %r{\A/((?:[^\./?#%]|(?:%[^2].|%[2][^Ee]))+)(?:\.|%2[Ee])((?:[^/?#%]|(?:%[^2].|%[2][^Ee]))+)\z} parses "/:file.:ext", "/pony.jpg", "file" => "pony", "ext" => "jpg" parses "/:file.:ext", "/pony%2Ejpg", "file" => "pony", "ext" => "jpg" fails "/:file.:ext", "/.jpg" converts "/:name.?:format?", %r{\A/((?:[^\./?#%]|(?:%[^2].|%[2][^Ee]))+)(?:\.|%2[Ee])?((?:[^/?#%]|(?:%[^2].|%[2][^Ee]))+)?\z} parses "/:name.?:format?", "/foo", "name" => "foo", "format" => nil parses "/:name.?:format?", "/foo.bar", "name" => "foo", "format" => "bar" parses "/:name.?:format?", "/foo%2Ebar", "name" => "foo", "format" => "bar" parses "/:name?.?:format", "/.bar", "name" => nil, "format" => "bar" parses "/:name?.?:format?", "/.bar", "name" => nil, "format" => "bar" parses "/:name?.:format?", "/.bar", "name" => nil, "format" => "bar" fails "/:name.:format", "/.bar" fails "/:name.?:format?", "/.bar" converts "/:user@?:host?", %r{\A/((?:[^@/?#%]|(?:%[^4].|%[4][^0]))+)(?:@|%40)?((?:[^@/?#%]|(?:%[^4].|%[4][^0]))+)?\z} parses "/:user@?:host?", "/foo@bar", "user" => "foo", "host" => "bar" parses "/:user@?:host?", "/foo.foo@bar", "user" => "foo.foo", "host" => "bar" parses "/:user@?:host?", "/foo@bar.bar", "user" => "foo", "host" => "bar.bar" # From https://gist.github.com/2154980#gistcomment-169469. # # converts "/:name(.:format)?", %r{\A/([^\.%2E/?#]+)(?:\(|%28)(?:\.|%2E)([^\.%2E/?#]+)(?:\)|%29)?\z} # parses "/:name(.:format)?", "/foo", "name" => "foo", "format" => nil # parses "/:name(.:format)?", "/foo.bar", "name" => "foo", "format" => "bar" fails "/:name(.:format)?", "/foo." parses "/:id/test.bar", "/3/test.bar", {"id" => "3"} parses "/:id/test.bar", "/2/test.bar", {"id" => "2"} parses "/:id/test.bar", "/2E/test.bar", {"id" => "2E"} parses "/:id/test.bar", "/2e/test.bar", {"id" => "2e"} parses "/:id/test.bar", "/%2E/test.bar", {"id" => "%2E"} parses '/10/:id', '/10/test', "id" => "test" parses '/10/:id', '/10/te.st', "id" => "te.st" parses '/10.1/:id', '/10.1/test', "id" => "test" parses '/10.1/:id', '/10.1/te.st', "id" => "te.st" parses '/:foo/:id', '/10.1/te.st', "foo" => "10.1", "id" => "te.st" parses '/:foo/:id', '/10.1.2/te.st', "foo" => "10.1.2", "id" => "te.st" parses '/:foo.:bar/:id', '/10.1/te.st', "foo" => "10", "bar" => "1", "id" => "te.st" fails '/:foo.:bar/:id', '/10.1.2/te.st' # We don't do crazy. parses '/:a/:b.?:c?', '/a/b', "a" => "a", "b" => "b", "c" => nil parses '/:a/:b.?:c?', '/a/b.c', "a" => "a", "b" => "b", "c" => "c" parses '/:a/:b.?:c?', '/a.b/c', "a" => "a.b", "b" => "c", "c" => nil parses '/:a/:b.?:c?', '/a.b/c.d', "a" => "a.b", "b" => "c", "c" => "d" fails '/:a/:b.?:c?', '/a.b/c.d/e' parses "/:file.:ext", "/pony%2ejpg", "file" => "pony", "ext" => "jpg" parses "/:file.:ext", "/pony%E6%AD%A3%2Ejpg", "file" => "pony%E6%AD%A3", "ext" => "jpg" parses "/:file.:ext", "/pony%e6%ad%a3%2ejpg", "file" => "pony%e6%ad%a3", "ext" => "jpg" parses "/:file.:ext", "/pony正%2Ejpg", "file" => "pony正", "ext" => "jpg" parses "/:file.:ext", "/pony正%2ejpg", "file" => "pony正", "ext" => "jpg" parses "/:file.:ext", "/pony正..jpg", "file" => "pony正", "ext" => ".jpg" fails "/:file.:ext", "/pony正.%2ejpg" converts "/:name.:format", %r{\A/((?:[^\./?#%]|(?:%[^2].|%[2][^Ee]))+)(?:\.|%2[Ee])((?:[^/?#%]|(?:%[^2].|%[2][^Ee]))+)\z} parses "/:name.:format", "/file.tar.gz", "name" => "file", "format" => "tar.gz" parses "/:name.:format1.:format2", "/file.tar.gz", "name" => "file", "format1" => "tar", "format2" => "gz" parses "/:name.:format1.:format2", "/file.temp.tar.gz", "name" => "file", "format1" => "temp", "format2" => "tar.gz" # From issue #688. # parses "/articles/10.1103/:doi", "/articles/10.1103/PhysRevLett.110.026401", "doi" => "PhysRevLett.110.026401" end sinatra-1.4.8/test/builder_test.rb0000644000004100000410000000430513044044066017211 0ustar www-datawww-datarequire 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 "#{$!.to_s}: skipping builder tests" end sinatra-1.4.8/test/creole_test.rb0000644000004100000410000000274013044044066017035 0ustar www-datawww-datarequire 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 "#{$!.to_s}: skipping creole tests" end sinatra-1.4.8/test/stylus_test.rb0000644000004100000410000000440713044044066017131 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) begin require 'stylus' require 'stylus/tilt' begin Stylus.compile '1' rescue RuntimeError raise LoadError, 'unable to find Stylus compiler' end class StylusTest < 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 "#{$!.to_s}: skipping stylus tests" end sinatra-1.4.8/test/public/0000755000004100000410000000000013044044066015453 5ustar www-datawww-datasinatra-1.4.8/test/public/hello+world.txt0000644000004100000410000000010113044044066020432 0ustar www-datawww-dataThis is a test intended for the + sign in urls for static servingsinatra-1.4.8/test/public/favicon.ico0000644000004100000410000000000013044044066017562 0ustar www-datawww-datasinatra-1.4.8/test/nokogiri_test.rb0000644000004100000410000000320213044044066017377 0ustar www-datawww-datarequire 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 "#{$!.to_s}: skipping nokogiri tests" end sinatra-1.4.8/test/streaming_test.rb0000644000004100000410000000702113044044066017552 0ustar www-datawww-datarequire 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-1.4.8/test/radius_test.rb0000644000004100000410000000253613044044066017056 0ustar www-datawww-datarequire 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 "#{$!.to_s}: skipping radius tests" end sinatra-1.4.8/test/haml_test.rb0000644000004100000410000000564013044044066016507 0ustar www-datawww-datarequire 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 "#{$!.to_s}: skipping haml tests" end sinatra-1.4.8/test/markdown_test.rb0000644000004100000410000000420313044044066017402 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) MarkdownTest = proc do def markdown_app(&block) mock_app do set :views, File.dirname(__FILE__) + '/views' get('/', &block) end get '/' end def setup Tilt.prefer engine, 'markdown', 'mkd', 'md' super end it 'uses the correct engine' do assert_equal engine, Tilt[:md] assert_equal engine, Tilt[:mkd] assert_equal engine, Tilt[:markdown] end it 'renders inline markdown strings' do markdown_app { markdown '# Hiya' } assert ok? assert_like "

Hiya

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

Hello From Markdown

", body end it "raises error if template not found" do mock_app { get('/') { markdown :no_such_template } } assert_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. map = Tilt.respond_to?(:lazy_map) ? Tilt.lazy_map['md'].map(&:first) : Tilt.mappings['md'] map.each do |t| begin t = eval(t) if t.is_a? String t.new { "" } klass = Class.new(Minitest::Test) { define_method(:engine) { t }} klass.class_eval(&MarkdownTest) name = t.name[/[^:]+$/].sub(/Template$/, '') << "Test" Object.const_set name, klass rescue LoadError, NameError warn "#{$!}: skipping markdown tests with #{t}" end end sinatra-1.4.8/test/yajl_test.rb0000644000004100000410000000347713044044066016533 0ustar www-datawww-datarequire 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 "#{$!.to_s}: skipping yajl tests" end sinatra-1.4.8/test/integration_helper.rb0000644000004100000410000001342113044044066020405 0ustar www-datawww-datarequire 'sinatra/base' require 'rbconfig' require 'open-uri' require 'net/http' require 'timeout' module IntegrationHelper class BaseServer extend Enumerable attr_accessor :server, :port, :pipe alias name server def self.all @all ||= [] end def self.each(&block) all.each(&block) end def self.run(server, port) new(server, port).run end def app_file File.expand_path('../integration/app.rb', __FILE__) end def environment "development" end def initialize(server, port) @installed, @pipe, @server, @port = nil, nil, server, port Server.all << self end def run return unless installed? kill @log = "" @pipe = IO.popen(command) @started = Time.now warn "#{server} up and running on port #{port}" if ping at_exit { kill } end def ping(timeout = 30) loop do return if alive? if Time.now - @started > timeout $stderr.puts command, log fail "timeout" else sleep 0.1 end end end def alive? 3.times { get('/ping') } true rescue Errno::ECONNREFUSED, Errno::ECONNRESET, EOFError, SystemCallError, OpenURI::HTTPError, Timeout::Error false end def get_stream(url = "/stream", &block) Net::HTTP.start '127.0.0.1', port do |http| request = Net::HTTP::Get.new url http.request request do |response| response.read_body(&block) end end end def get_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 get(url) Timeout.timeout(1) { open("http://127.0.0.1:#{port}#{url}").read } end def log @log ||= "" loop { @log << @pipe.read_nonblock(1) } rescue Exception @log end def installed? return @installed unless @installed.nil? s = server == 'HTTP' ? 'net/http/server' : server require s @installed = true rescue LoadError warn "#{server} is not installed, skipping integration tests" @installed = false end def command @command ||= begin cmd = ["RACK_ENV=#{environment}", "exec"] if RbConfig.respond_to? :ruby cmd << RbConfig.ruby.inspect else file, dir = RbConfig::CONFIG.values_at('ruby_install_name', 'bindir') cmd << File.expand_path(file, dir).inspect end cmd << "-w" unless thin? || net_http_server? cmd << "-I" << File.expand_path('../../lib', __FILE__).inspect cmd << app_file.inspect << '-s' << server << '-o' << '127.0.0.1' << '-p' << port cmd << "-e" << environment.to_s << '2>&1' cmd.join " " end end def kill return unless pipe Process.kill("KILL", pipe.pid) rescue NotImplementedError system "kill -9 #{pipe.pid}" rescue Errno::ESRCH end def webrick? name.to_s == "webrick" end def thin? name.to_s == "thin" end def puma? name.to_s == "puma" end def trinidad? name.to_s == "trinidad" end def net_http_server? name.to_s == 'HTTP' end def warnings log.scan(%r[(?:\(eval|lib/sinatra).*warning:.*$]) end def run_test(target, &block) retries ||= 3 target.server = self run unless alive? target.instance_eval(&block) rescue Exception => error retries -= 1 kill retries < 0 ? retry : raise(error) end end if RUBY_ENGINE == "jruby" class JRubyServer < BaseServer def start_vm require 'java' # Create a new container, set load paths and env # SINGLETHREAD means create a new runtime vm = org.jruby.embed.ScriptingContainer.new(org.jruby.embed.LocalContextScope::SINGLETHREAD) vm.load_paths = [File.expand_path('../../lib', __FILE__)] vm.environment = ENV.merge('RACK_ENV' => environment.to_s) # This ensures processing of RUBYOPT which activates Bundler vm.provider.ruby_instance_config.process_arguments [] vm.argv = ['-s', server.to_s, '-o', '127.0.0.1', '-p', port.to_s, '-e', environment.to_s] # Set stdout/stderr so we can retrieve log @pipe = java.io.ByteArrayOutputStream.new vm.output = java.io.PrintStream.new(@pipe) vm.error = java.io.PrintStream.new(@pipe) Thread.new do # Hack to ensure that Kernel#caller has the same info as # when run from command-line, for Sinatra::Application.app_file. # Also, line numbers are zero-based in JRuby's parser vm.provider.runtime.current_context.set_file_and_line(app_file, 0) # Run the app vm.run_scriptlet org.jruby.embed.PathType::ABSOLUTE, app_file # terminate launches at_exit hooks which start server vm.terminate end end def run return unless installed? kill @thread = start_vm @started = Time.now warn "#{server} up and running on port #{port}" if ping at_exit { kill } end def log String.from_java_bytes @pipe.to_byte_array end def kill @thread.kill if @thread @thread = nil end end Server = JRubyServer else Server = BaseServer end def it(message, &block) Server.each do |server| next unless server.installed? super("with #{server.name}: #{message}") { server.run_test(self, &block) } end end def self.extend_object(obj) super base_port = 5000 + Process.pid % 100 Sinatra::Base.server.each_with_index do |server, index| Server.run(server, base_port+index) end end end sinatra-1.4.8/test/rdoc_test.rb0000644000004100000410000000327013044044066016512 0ustar www-datawww-datarequire 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 "#{$!.to_s}: skipping rdoc tests" end sinatra-1.4.8/test/middleware_test.rb0000644000004100000410000000316413044044066017702 0ustar www-datawww-datarequire 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 end sinatra-1.4.8/test/erb_test.rb0000644000004100000410000000506513044044066016337 0ustar www-datawww-datarequire 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 'erubis' class ErubisTest < ERBTest def engine; Tilt::ErubisTemplate end end rescue LoadError warn "#{$!.to_s}: skipping erubis tests" end sinatra-1.4.8/test/contest.rb0000644000004100000410000000624513044044066016210 0ustar www-datawww-data# Copyright (c) 2009 Damian Janowski and Michel Martens for Citrusbyte # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. require "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-1.4.8/test/response_test.rb0000644000004100000410000000354013044044066017421 0ustar www-datawww-data# 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 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-1.4.8/test/mapped_error_test.rb0000644000004100000410000001616013044044066020244 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) class FooError < RuntimeError end class FooNotFound < Sinatra::NotFound end class FooSpecialError < RuntimeError def http_status; 501 end end class FooStatusOutOfRangeError < RuntimeError def code; 4000 end end class FooWithCode < RuntimeError def code; 419 end end class FirstError < RuntimeError; end class SecondError < RuntimeError; end class MappedErrorTest < 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-1.4.8/test/wlang_test.rb0000644000004100000410000000366313044044066016701 0ustar www-datawww-datarequire 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 "#{$!.to_s}: skipping wlang tests" end sinatra-1.4.8/test/rack_test.rb0000644000004100000410000000204113044044066016476 0ustar www-datawww-datarequire 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-1.4.8/test/helpers_test.rb0000644000004100000410000013663013044044066017234 0ustar www-datawww-datarequire 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, 205, 304].include? code block ||= proc { } mock_app do get('/') do status code instance_eval(&block).inspect end end get '/' end describe 'status' do it 'sets the response status code' do status_app 207 assert_equal 207, response.status end end describe 'not_found?' do it 'is true for status == 404' do status_app(404) { not_found? } assert_body 'true' end it 'is false for status gt 404' do status_app(405) { not_found? } assert_body 'false' end it 'is false for status lt 404' do status_app(403) { not_found? } assert_body 'false' end end describe 'informational?' do it 'is true for 1xx status' do status_app(100 + rand(100)) { informational? } assert_body 'true' end it 'is false for status > 199' do status_app(200 + rand(400)) { informational? } assert_body 'false' end end describe 'success?' do it 'is true for 2xx status' do status_app(200 + rand(100)) { success? } assert_body 'true' end it 'is false for status < 200' do status_app(100 + rand(100)) { success? } assert_body 'false' end it 'is false for status > 299' do status_app(300 + rand(300)) { success? } assert_body 'false' end end describe 'redirect?' do it 'is true for 3xx status' do status_app(300 + rand(100)) { redirect? } assert_body 'true' end it 'is false for status < 300' do status_app(200 + rand(100)) { redirect? } assert_body 'false' end it 'is false for status > 399' do status_app(400 + rand(200)) { redirect? } assert_body 'false' end end describe 'client_error?' do it 'is true for 4xx status' do status_app(400 + rand(100)) { client_error? } assert_body 'true' end it 'is false for status < 400' do status_app(200 + rand(200)) { client_error? } assert_body 'false' end it 'is false for status > 499' do status_app(500 + rand(100)) { client_error? } assert_body 'false' end end describe 'server_error?' do it 'is true for 5xx status' do status_app(500 + rand(100)) { server_error? } assert_body 'true' end it 'is false for status < 500' do status_app(200 + rand(300)) { server_error? } assert_body 'false' end end describe 'body' do it 'takes a block for deferred body generation' do mock_app do get('/') { body { 'Hello World' } } end get '/' assert_equal 'Hello World', body end it 'takes a String, Array, or other object responding to #each' do mock_app { get('/') { body 'Hello World' } } get '/' assert_equal 'Hello World', body end it 'can be used with other objects' do mock_app do get '/' do body :hello => 'from json' end after do if Hash === response.body body response.body[:hello] end end end get '/' assert_body 'from json' end it 'can be set in after filter' do mock_app do get('/') { body 'route' } after { body 'filter' } end get '/' assert_body 'filter' end end describe 'redirect' do it 'uses a 302 when only a path is given' do mock_app do get('/') do redirect '/foo' fail 'redirect should halt' end end get '/' assert_equal 302, status assert_equal '', body assert_equal 'http://example.org/foo', response['Location'] end it 'uses the code given when specified' do mock_app do get('/') do redirect '/foo', 301 fail 'redirect should halt' end end get '/' assert_equal 301, status assert_equal '', body assert_equal 'http://example.org/foo', response['Location'] end it 'redirects back to request.referer when passed back' do mock_app { get('/try_redirect') { redirect back } } request = Rack::MockRequest.new(@app) response = request.get('/try_redirect', 'HTTP_REFERER' => '/foo') assert_equal 302, response.status assert_equal 'http://example.org/foo', response['Location'] end it 'redirects using a non-standard HTTP port' do mock_app { get('/') { redirect '/foo' } } request = Rack::MockRequest.new(@app) response = request.get('/', 'SERVER_PORT' => '81') assert_equal 'http://example.org:81/foo', response['Location'] end it 'redirects using a non-standard HTTPS port' do mock_app { get('/') { redirect '/foo' } } request = Rack::MockRequest.new(@app) response = request.get('/', 'SERVER_PORT' => '444') assert_equal 'http://example.org:444/foo', response['Location'] end it 'uses 303 for post requests if request is HTTP 1.1' do mock_app { post('/') { redirect '/'} } post('/', {}, 'HTTP_VERSION' => 'HTTP/1.1') assert_equal 303, status assert_equal '', body assert_equal 'http://example.org/', response['Location'] end it 'uses 302 for post requests if request is HTTP 1.0' do mock_app { post('/') { redirect '/'} } post('/', {}, 'HTTP_VERSION' => 'HTTP/1.0') assert_equal 302, status assert_equal '', body assert_equal 'http://example.org/', response['Location'] end it 'works behind a reverse proxy' do mock_app { get('/') { redirect '/foo' } } request = Rack::MockRequest.new(@app) response = request.get('/', 'HTTP_X_FORWARDED_HOST' => 'example.com', 'SERVER_PORT' => '8080') assert_equal 'http://example.com/foo', response['Location'] end it 'accepts absolute URIs' do mock_app do get('/') do redirect 'http://google.com' fail 'redirect should halt' end end get '/' assert_equal 302, status assert_equal '', body assert_equal 'http://google.com', response['Location'] end it 'accepts absolute URIs with a different schema' do mock_app do get('/') do redirect 'mailto:jsmith@example.com' fail 'redirect should halt' end end get '/' assert_equal 302, status assert_equal '', body assert_equal 'mailto:jsmith@example.com', response['Location'] end it 'accepts a URI object instead of a String' do mock_app do get('/') { redirect URI.parse('http://sinatrarb.com') } end get '/' assert_equal 302, status assert_equal '', body assert_equal 'http://sinatrarb.com', response['Location'] end end describe 'error' do it 'sets a status code and halts' do mock_app do get('/') do error 501 fail 'error should halt' end end get '/' assert_equal 501, status assert_equal '', body end it 'takes an optional body' do mock_app do get('/') do error 501, 'FAIL' fail 'error should halt' end end get '/' assert_equal 501, status assert_equal 'FAIL', body end it 'should not invoke error handler when setting status inside an error handler' do mock_app do disable :raise_errors not_found do body "not_found handler" status 404 end error do body "error handler" status 404 end get '/' do raise end end get '/' assert_equal 404, status assert_equal 'error handler', body end it 'should not reset the content-type to html for error handlers' do mock_app do disable :raise_errors before { content_type "application/json" } 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_equal nil, response.headers['X-Cascade'] end end describe 'headers' do it 'sets headers on the response object when given a Hash' do mock_app do get('/') do headers 'X-Foo' => 'bar', 'X-Baz' => 'bling' 'kthx' end end get '/' assert ok? assert_equal 'bar', response['X-Foo'] assert_equal 'bling', response['X-Baz'] assert_equal 'kthx', body end it 'returns the response headers hash when no hash provided' do mock_app do get('/') do headers['X-Foo'] = 'bar' 'kthx' end end get '/' assert ok? assert_equal 'bar', response['X-Foo'] end end describe 'session' do it 'uses the existing rack.session' do mock_app do get('/') do session[:foo] end end get('/', {}, { 'rack.session' => { :foo => 'bar' } }) assert_equal 'bar', body end it 'creates a new session when none provided' do mock_app do enable :sessions get('/') do assert session[:foo].nil? session[:foo] = 'bar' redirect '/hi' end get('/hi') do "hi #{session[:foo]}" end end get '/' follow_redirect! assert_equal 'hi bar', body end it 'inserts session middleware' do mock_app do enable :sessions get('/') do assert env['rack.session'] assert env['rack.session.options'] 'ok' end end get '/' assert_body 'ok' end it 'sets a default session secret' do mock_app do enable :sessions get('/') do secret = env['rack.session.options'][:secret] assert secret assert_equal secret, settings.session_secret 'ok' end end get '/' assert_body 'ok' end it 'allows disabling session secret' do mock_app do enable :sessions disable :session_secret get('/') do assert !env['rack.session.options'].include?(:session_secret) 'ok' end end # Silence warnings since Rack::Session::Cookie complains about the non-present session secret silence_warnings do get '/' end assert_body 'ok' end it 'accepts an options hash' do mock_app do set :sessions, :foo => :bar get('/') do assert_equal env['rack.session.options'][:foo], :bar 'ok' end end get '/' assert_body 'ok' end end describe 'mime_type' do include Sinatra::Helpers it "looks up mime types in Rack's MIME registry" do Rack::Mime::MIME_TYPES['.foo'] = 'application/foo' assert_equal 'application/foo', mime_type('foo') assert_equal 'application/foo', mime_type('.foo') assert_equal 'application/foo', mime_type(:foo) end it 'returns nil when given nil' do assert mime_type(nil).nil? end it 'returns nil when media type not registered' do assert mime_type(:bizzle).nil? end it 'returns the argument when given a media type string' do assert_equal 'text/plain', mime_type('text/plain') end it 'turns AcceptEntry into String' do type = mime_type(Sinatra::Request::AcceptEntry.new('text/plain')) assert_equal String, type.class assert_equal 'text/plain', type end end test 'Base.mime_type registers mime type' do mock_app do mime_type :foo, 'application/foo' get('/') do "foo is #{mime_type(:foo)}" end end get '/' assert_equal 'foo is application/foo', body end describe 'content_type' do it 'sets the Content-Type header' do mock_app do get('/') do content_type 'text/plain' 'Hello World' end end get '/' assert_equal 'text/plain;charset=utf-8', response['Content-Type'] assert_equal 'Hello World', body end it 'takes media type parameters (like charset=)' do mock_app do get('/') do content_type 'text/html', :charset => 'latin1' "

Hello, World

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

Hello, World

", body end it "looks up symbols in Rack's mime types dictionary" do Rack::Mime::MIME_TYPES['.foo'] = 'application/foo' mock_app do get('/foo.xml') do content_type :foo "I AM FOO" end end get '/foo.xml' assert ok? assert_equal 'application/foo', response['Content-Type'] assert_equal 'I AM FOO', body end it 'fails when no mime type is registered for the argument provided' do mock_app do get('/foo.xml') do content_type :bizzle "I AM FOO" end end assert_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 "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 end describe 'logger' do it 'logging works when logging is enabled' do mock_app do enable :logging get('/') do logger.info "Program started" logger.warn "Nothing to do!" end end io = StringIO.new get '/', {}, 'rack.errors' => io assert io.string.include?("INFO -- : Program started") assert io.string.include?("WARN -- : Nothing to do") end it 'logging works when logging is disable, but no output is produced' do mock_app do disable :logging get('/') do logger.info "Program started" logger.warn "Nothing to do!" end end io = StringIO.new get '/', {}, 'rack.errors' => io assert !io.string.include?("INFO -- : Program started") assert !io.string.include?("WARN -- : Nothing to do") end it 'does not create a logger when logging is set to nil' do mock_app do set :logging, nil get('/') { logger.inspect } end get '/' assert_body 'nil' end end module ::HelperOne; def one; '1'; end; end module ::HelperTwo; def two; '2'; end; end describe 'Adding new helpers' do it 'takes a list of modules to mix into the app' do mock_app do helpers ::HelperOne, ::HelperTwo get('/one') { one } get('/two') { two } end get '/one' assert_equal '1', body get '/two' assert_equal '2', body end it 'takes a block to mix into the app' do mock_app do helpers do def foo 'foo' end end get('/') { foo } end get '/' assert_equal 'foo', body end it 'evaluates the block in class context so that methods can be aliased' do mock_app do helpers { alias_method :h, :escape_html } get('/') { h('42 < 43') } end get '/' assert ok? assert_equal '42 < 43', body end end end sinatra-1.4.8/test/delegator_test.rb0000644000004100000410000000742313044044066017535 0ustar www-datawww-datarequire 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-1.4.8/test/integration_test.rb0000644000004100000410000000556313044044066020115 0ustar www-datawww-datarequire File.expand_path('../helper', __FILE__) require File.expand_path('../integration_helper', __FILE__) # These tests start a real server and talk to it over TCP. # Every test runs with every detected server. # # See test/integration/app.rb for the code of the app we test against. class IntegrationTest < 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? 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? 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-1.4.8/test/mediawiki_test.rb0000644000004100000410000000336313044044066017531 0ustar www-datawww-datarequire 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 "#{$!.to_s}: skipping mediawiki tests" end sinatra-1.4.8/test/readme_test.rb0000644000004100000410000000526513044044066017026 0ustar www-datawww-data# 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-1.4.8/test/scss_test.rb0000644000004100000410000000447013044044066016541 0ustar www-datawww-datarequire 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 "#{$!.to_s}: skipping scss tests" end sinatra-1.4.8/test/templates_test.rb0000644000004100000410000002413613044044066017565 0ustar www-datawww-data# encoding: UTF-8 require File.expand_path('../helper', __FILE__) File.delete(File.dirname(__FILE__) + '/views/layout.test') rescue nil class TestTemplate < Tilt::Template def prepare end def evaluate(scope, locals={}, &block) inner = block ? block.call : '' data + inner end Tilt.register 'test', self end class TemplatesTest < 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 '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 per template engine' do render_app(:str => { :content_type => :txt }) { render :str, 'foo' } assert_equal 'text/plain;charset=utf-8', response['Content-Type'] end it 'setting default content type does not affect other template engines' do render_app(:str => { :content_type => :txt }) { render :test, 'foo' } assert_equal 'text/html;charset=utf-8', response['Content-Type'] end it 'setting default content type per template engine does not override content_type' do render_app :str => { :content_type => :txt } do content_type :html render :str, 'foo' end assert_equal 'text/html;charset=utf-8', response['Content-Type'] end it 'uses templates in superclasses before subclasses' do base = Class.new(Sinatra::Base) base.template(:foo) { 'template in superclass' } assert_equal 'template in superclass', base.templates[:foo].first.call mock_app(base) do set :views, File.dirname(__FILE__) + '/views' template(:foo) { 'template in subclass' } get('/') { render :test, :foo } end assert_equal 'template in subclass', @app.templates[:foo].first.call get '/' assert ok? assert_equal 'template in subclass', body end it "is possible to use a different engine for the layout than for the template itself explicitly" do render_app do settings.template(:layout) { 'Hello <%= yield %>!' } render :str, "<%= 'World' %>", :layout_engine => :erb end assert_equal "Hello <%= 'World' %>!", body end it "is possible to use a different engine for the layout than for the template itself globally" do render_app :str => { :layout_engine => :erb } do settings.template(:layout) { 'Hello <%= yield %>!' } render :str, "<%= 'World' %>" end assert_equal "Hello <%= 'World' %>!", body end it "does not leak the content type to the template" do render_app :str => { :layout_engine => :erb } do settings.template(:layout) { 'Hello <%= yield %>!' } render :str, "<%= 'World' %>", :content_type => :txt end assert_equal "text/html;charset=utf-8", headers['Content-Type'] end it "is possible to register another template" do Tilt.register "html.erb", Tilt[:erb] render_app { render :erb, :calc } assert_equal '2', body end it "passes scope to the template" do mock_app do template(:scoped) { 'Hello <%= foo %>' } get('/') do some_scope = Object.new def some_scope.foo() 'World!' end erb :scoped, :scope => some_scope end end get '/' assert ok? assert_equal 'Hello World!', body end it "is possible to use custom logic for finding template files" do mock_app do set :views, ["a", "b"].map { |d| File.dirname(__FILE__) + '/views/' + d } def find_template(views, name, engine, &block) Array(views).each { |v| super(v, name, engine, &block) } end get('/:name') { render(:str, params[:name].to_sym) } end get '/in_a' assert_body 'Gimme an A!' get '/in_b' assert_body 'Gimme a B!' end end # __END__ : this is not the real end of the script. __END__ @@ foo this is foo @@ bar There's a space after 'bar'! @@ foo bar this is not foo @@ umlaut Den som tror at hemma det är där man bor har aldrig vart hos mig. @@ layout X = yield X sinatra-1.4.8/test/liquid_test.rb0000644000004100000410000000364713044044066017062 0ustar www-datawww-datarequire 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 "#{$!.to_s}: skipping liquid tests" end sinatra-1.4.8/test/base_test.rb0000644000004100000410000001144613044044066016501 0ustar www-datawww-datarequire 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-1.4.8/test/markaby_test.rb0000644000004100000410000000372713044044066017220 0ustar www-datawww-datarequire 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 "#{$!.to_s}: skipping markaby tests" end sinatra-1.4.8/test/integration/0000755000004100000410000000000013044044066016520 5ustar www-datawww-datasinatra-1.4.8/test/integration/app.rb0000644000004100000410000000234213044044066017626 0ustar www-datawww-data$stderr.puts "loading" require 'sinatra' configure do set :foo, :bar end get '/app_file' do content_type :txt settings.app_file end get '/ping' do 'pong' end get '/stream' do stream do |out| sleep 0.1 out << "a" sleep 1.2 out << "b" end end get '/mainonly' do object = Object.new begin object.send(:get, '/foo') { } 'false' rescue NameError 'true' end end set :out, nil get '/async' do stream(:keep_open) { |o| (settings.out = o) << "hi!" } end get '/send' do settings.out << params[:msg] if params[:msg] settings.out.close if params[:close] "ok" end 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-1.4.8/test/sinatra_test.rb0000644000004100000410000000054613044044066017227 0ustar www-datawww-datarequire 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-1.4.8/test/less_test.rb0000644000004100000410000000347013044044066016533 0ustar www-datawww-datarequire 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 "#{$!.to_s}: skipping less tests" end sinatra-1.4.8/test/result_test.rb0000644000004100000410000000334213044044066017101 0ustar www-datawww-datarequire 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 Fixnum status code" do mock_app { get('/') { 205 } } get '/' assert_equal 205, status assert_equal '', body end end sinatra-1.4.8/test/sass_test.rb0000644000004100000410000000635613044044066016544 0ustar www-datawww-datarequire 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 "#{$!.to_s}: skipping sass tests" end sinatra-1.4.8/test/settings_test.rb0000644000004100000410000003556713044044066017441 0ustar www-datawww-datarequire 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 end describe 'run' do it 'is disabled on Base' do assert ! @base.run? end it 'is enabled on Application except in test environment' do assert @application.run? @application.set :environment, :test assert ! @application.run? end end describe 'raise_errors' do it 'is enabled on Base only in test' do assert ! @base.raise_errors? @base.set(:environment, :test) assert @base.raise_errors? end it 'is enabled on Application only in test' do assert ! @application.raise_errors? @application.set(:environment, :test) assert @application.raise_errors? end end describe 'show_exceptions' do it 'is disabled on Base except under development' do assert ! @base.show_exceptions? @base.environment = :development assert @base.show_exceptions? end it 'is disabled on Application except in development' do assert ! @application.show_exceptions? @application.set(:environment, :development) assert @application.show_exceptions? end it 'returns a friendly 500' do klass = Sinatra.new(Sinatra::Application) mock_app(klass) { enable :show_exceptions get '/' do raise StandardError end } get '/' assert_equal 500, status assert body.include?("StandardError") assert body.include?("show_exceptions setting") end it 'does not override app-specified error handling when set to :after_handler' do ran = false mock_app do set :show_exceptions, :after_handler error(RuntimeError) { ran = true } get('/') { raise RuntimeError } end get '/' assert_equal 500, status assert ran end it 'does catch any other exceptions when set to :after_handler' do ran = false mock_app do set :show_exceptions, :after_handler error(RuntimeError) { ran = true } get('/') { raise ArgumentError } end get '/' assert_equal 500, status assert !ran end end describe 'dump_errors' do it 'is disabled on Base in test' do @base.environment = :test assert ! @base.dump_errors? @base.environment = :development assert @base.dump_errors? @base.environment = :production assert @base.dump_errors? end it 'dumps exception with backtrace to rack.errors' do klass = Sinatra.new(Sinatra::Application) mock_app(klass) { enable :dump_errors disable :raise_errors error do error = @env['rack.errors'].instance_variable_get(:@error) error.rewind error.read end get '/' do raise end } get '/' assert body.include?("RuntimeError") && body.include?("settings_test.rb") end it 'does not dump 404 errors' do klass = Sinatra.new(Sinatra::Application) mock_app(klass) { enable :dump_errors disable :raise_errors error do error = @env['rack.errors'].instance_variable_get(:@error) error.rewind error.read end get '/' do raise Sinatra::NotFound end } get '/' assert !body.include?("NotFound") && !body.include?("settings_test.rb") end end describe 'sessions' do it 'is disabled on Base' do assert ! @base.sessions? end it 'is disabled on Application' do assert ! @application.sessions? end end describe 'logging' do it 'is disabled on Base' do assert ! @base.logging? end it 'is enabled on Application except in test environment' do assert @application.logging? @application.set :environment, :test assert ! @application.logging end end describe 'static' do it 'is disabled on Base by default' do assert ! @base.static? end it 'is enabled on Base when public_folder is set and exists' do @base.set :environment, :development @base.set :public_folder, File.dirname(__FILE__) assert @base.static? end it 'is enabled on Base when root is set and root/public_folder exists' do @base.set :environment, :development @base.set :root, File.dirname(__FILE__) assert @base.static? end it 'is disabled on Application by default' do assert ! @application.static? end it 'is enabled on Application when public_folder is set and exists' do @application.set :environment, :development @application.set :public_folder, File.dirname(__FILE__) assert @application.static? end it 'is enabled on Application when root is set and root/public_folder exists' do @application.set :environment, :development @application.set :root, File.dirname(__FILE__) assert @application.static? end it 'is possible to use Module#public' do @base.send(:define_method, :foo) { } @base.send(:private, :foo) assert !@base.public_method_defined?(:foo) @base.send(:public, :foo) assert @base.public_method_defined?(:foo) end it 'is possible to use the keyword public in a sinatra app' do app = Sinatra.new do private def priv; end public def pub; end end assert !app.public_method_defined?(:priv) assert app.public_method_defined?(:pub) end end describe 'bind' do it 'defaults to 0.0.0.0' do assert_equal '0.0.0.0', @base.bind assert_equal '0.0.0.0', @application.bind end end describe 'port' do it 'defaults to 4567' do assert_equal 4567, @base.port assert_equal 4567, @application.port end end describe 'server' do it 'includes webrick' do assert @base.server.include?('webrick') assert @application.server.include?('webrick') end it 'includes puma' do assert @base.server.include?('puma') assert @application.server.include?('puma') end it 'includes thin' do next if RUBY_ENGINE == 'jruby' assert @base.server.include?('thin') assert @application.server.include?('thin') end end describe 'app_file' do it 'is nil for base classes' do assert_nil Sinatra::Base.app_file assert_nil Sinatra::Application.app_file end it 'defaults to the file subclassing' do assert_equal File.expand_path(__FILE__), Sinatra.new.app_file end end describe 'root' do it 'is nil if app_file is not set' do assert @base.root.nil? assert @application.root.nil? end it 'is equal to the expanded basename of app_file' do @base.app_file = __FILE__ assert_equal File.expand_path(File.dirname(__FILE__)), @base.root @application.app_file = __FILE__ assert_equal File.expand_path(File.dirname(__FILE__)), @application.root end end describe 'views' do it 'is nil if root is not set' do assert @base.views.nil? assert @application.views.nil? end it 'is set to root joined with views/' do @base.root = File.dirname(__FILE__) assert_equal File.dirname(__FILE__) + "/views", @base.views @application.root = File.dirname(__FILE__) assert_equal File.dirname(__FILE__) + "/views", @application.views end end describe 'public_folder' do it 'is nil if root is not set' do assert @base.public_folder.nil? assert @application.public_folder.nil? end it 'is set to root joined with public/' do @base.root = File.dirname(__FILE__) assert_equal File.dirname(__FILE__) + "/public", @base.public_folder @application.root = File.dirname(__FILE__) assert_equal File.dirname(__FILE__) + "/public", @application.public_folder end end describe 'public_dir' do it 'is an alias for public_folder' do @base.public_dir = File.dirname(__FILE__) assert_equal File.dirname(__FILE__), @base.public_dir assert_equal @base.public_folder, @base.public_dir @application.public_dir = File.dirname(__FILE__) assert_equal File.dirname(__FILE__), @application.public_dir assert_equal @application.public_folder, @application.public_dir end end describe 'lock' do it 'is disabled by default' do assert ! @base.lock? assert ! @application.lock? end end describe 'protection' do class MiddlewareTracker < Rack::Builder def self.track Rack.send :remove_const, :Builder Rack.const_set :Builder, MiddlewareTracker MiddlewareTracker.used.clear yield ensure Rack.send :remove_const, :Builder Rack.const_set :Builder, MiddlewareTracker.superclass end def self.used @used ||= [] end def use(middleware, *) MiddlewareTracker.used << middleware super end end it 'sets up Rack::Protection' do MiddlewareTracker.track do Sinatra::Base.new assert_include MiddlewareTracker.used, Rack::Protection end end it 'sets up Rack::Protection::PathTraversal' do MiddlewareTracker.track do Sinatra::Base.new assert_include MiddlewareTracker.used, Rack::Protection::PathTraversal end end it 'does not set up Rack::Protection::PathTraversal when disabling it' do MiddlewareTracker.track do Sinatra.new { set :protection, :except => :path_traversal }.new assert_include MiddlewareTracker.used, Rack::Protection assert !MiddlewareTracker.used.include?(Rack::Protection::PathTraversal) end end it 'sets up RemoteToken if sessions are enabled' do MiddlewareTracker.track do Sinatra.new { enable :sessions }.new assert_include MiddlewareTracker.used, Rack::Protection::RemoteToken end end it 'does not set up RemoteToken if sessions are disabled' do MiddlewareTracker.track do Sinatra.new.new assert !MiddlewareTracker.used.include?(Rack::Protection::RemoteToken) end end it 'sets up RemoteToken if it is configured to' do MiddlewareTracker.track do Sinatra.new { set :protection, :session => true }.new assert_include MiddlewareTracker.used, Rack::Protection::RemoteToken end end end end sinatra-1.4.8/test/slim_test.rb0000644000004100000410000000515313044044066016531 0ustar www-datawww-datarequire 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 "#{$!.to_s}: skipping slim tests" end sinatra-1.4.8/test/textile_test.rb0000644000004100000410000000300313044044066017233 0ustar www-datawww-datarequire 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 "#{$!.to_s}: skipping textile tests" end sinatra-1.4.8/test/extensions_test.rb0000644000004100000410000000530213044044066017760 0ustar www-datawww-datarequire 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-1.4.8/test/asciidoctor_test.rb0000644000004100000410000000363513044044066020073 0ustar www-datawww-datarequire 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 "#{$!.to_s}: skipping asciidoc tests" end sinatra-1.4.8/test/filter_test.rb0000644000004100000410000002553013044044066017053 0ustar www-datawww-datarequire 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 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-1.4.8/test/static_test.rb0000644000004100000410000002063213044044066017053 0ustar www-datawww-datarequire 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_equal( nil, response['Content-Range'], "Invalid range '#{http_range}' should be ignored" ) end end it 'returns error 416 for unsatisfiable range requests' do # An unsatisfiable request is one that specifies a start that's at or past the end of the file. length = File.read(__FILE__).length ["bytes=888888-", "bytes=888888-999999", "bytes=#{length}-#{length}"].each do |http_range| request = Rack::MockRequest.new(@app) response = request.get("/#{File.basename(__FILE__)}", 'HTTP_RANGE' => http_range) assert_equal( 416, response.status, "Unsatisfiable range '#{http_range}' should return 416" ) assert_equal( "bytes */#{length}", response['Content-Range'], "416 response should include actual length" ) end end it 'does not include static cache control headers by default' do env = Rack::MockRequest.env_for("/#{File.basename(__FILE__)}") _, 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__)}") status, headers, body = @app.call(env) assert headers.has_key?('Cache-Control') assert_equal headers['Cache-Control'], 'public' @app.set( :static_cache_control, [:public, :must_revalidate, {:max_age => 300}] ) env = Rack::MockRequest.env_for("/#{File.basename(__FILE__)}") status, headers, body = @app.call(env) assert headers.has_key?('Cache-Control') assert_equal( headers['Cache-Control'], 'public, must-revalidate, max-age=300' ) end 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-1.4.8/test/request_test.rb0000644000004100000410000000707313044044066017260 0ustar www-datawww-datarequire 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::Base.new!.send(:indifferent_hash).replace(request.params) dumped = Marshal.dump(request.params) assert_equal 'bar', Marshal.load(dumped)['foo'] end it "exposes the preferred type's parameters" do request = Sinatra::Request.new( 'HTTP_ACCEPT' => 'image/jpeg; compress=0.25' ) assert_equal({ 'compress' => '0.25' }, request.preferred_type.params) end it "makes accept types behave like strings" do request = Sinatra::Request.new('HTTP_ACCEPT' => 'image/jpeg; compress=0.25') assert request.accept?('image/jpeg') assert_equal 'image/jpeg', request.preferred_type.to_s assert_equal 'image/jpeg; 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-1.4.8/test/encoding_test.rb0000644000004100000410000000115313044044066017347 0ustar www-datawww-data# 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-1.4.8/sinatra.gemspec0000644000004100000410000000176713044044066016237 0ustar www-datawww-data$LOAD_PATH.unshift File.expand_path('../lib', __FILE__) require 'sinatra/version' Gem::Specification.new 'sinatra', Sinatra::VERSION do |s| s.description = "Sinatra is a DSL for quickly creating web applications in Ruby with minimal effort." s.summary = "Classy web-development dressed in a DSL" s.authors = ["Blake Mizerany", "Ryan Tomayko", "Simon Rozet", "Konstantin Haase"] s.email = "sinatrarb@googlegroups.com" s.homepage = "http://www.sinatrarb.com/" s.license = 'MIT' s.files = `git ls-files`.split("\n") - %w[.gitignore .travis.yml] s.test_files = s.files.select { |p| p =~ /^test\/.*_test.rb/ } s.extra_rdoc_files = s.files.select { |p| p =~ /^README/ } << 'LICENSE' s.rdoc_options = %w[--line-numbers --inline-source --title Sinatra --main README.rdoc --encoding=UTF-8] s.add_dependency 'rack', '~> 1.5' s.add_dependency 'tilt', '>= 1.3', '< 3' s.add_dependency 'rack-protection', '~> 1.4' end sinatra-1.4.8/.yardopts0000644000004100000410000000016013044044066015061 0ustar www-datawww-data--readme README.md --title 'Sinatra API Documentation' --charset utf-8 --markup markdown 'lib/**/*.rb' - '*.md' sinatra-1.4.8/README.zh.md0000644000004100000410000021525113044044066015123 0ustar www-datawww-data# 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 /\A\/hello\/([\w]+)\z/ 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 ``` 顺便一提,除非你禁用了路径遍历攻击防护(见下文),请求路径可能在匹配路由前发生改变。 ### 条件 路由可以包含各种匹配条件,比如 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 状态码: * 一个包含三个元素的数组: `[状态 (Fixnum), 响应首部 (Hash), 响应主体 (可以响应 #each 方法)]` * 一个包含两个元素的数组: `[状态 (Fixnum), 响应主体 (可以响应 #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 ``` 请注意 `enable :sessions` 实际将所有的数据保存在一个 cookie 中。 这可能并不总是你想要的(cookie 中存储大量的数据会增加你的流量)。 你可以使用任何 Rack session 中间件:要达到此目的,**不要**使用 `enable :sessions`, 而是按照自己的需要引入想使用的中间件: ```ruby use Rack::Session::Pool, :expire_after => 2592000 get '/' do "value = " << session['value'].inspect end get '/:value' do session['value'] = params['value'] end ``` 为提高安全性,cookie 中的会话数据会被一个会话密码保护。Sinatra 会为你生成一个随机的密码。 然而,每次启动应用时,该密码都会变化,你也可以自己设置该密码,以便所有的应用实例共享: ``` set :session_secret, 'super secret' ``` 如果你想进一步配置会话,可以在设置 `sessions` 时提供一个选项 hash 作为第二个参数: ``` set :sessions, :domain => 'foo.com' ``` 为了在 foo.com 的子域名间共享会话数据,可以在域名前添加一个 *.*: ```ruby set :sessions, :domain => '.foo.com' ``` ### 中断请求 要想在过滤器或路由中立即中断一个请求: ```ruby halt ``` 你也可以指定中断时的状态码: ```ruby halt 410 ``` 或者响应主体: ```ruby halt 'this will be the body' ``` 或者同时指定两者: ```ruby halt 401, 'go away!' ``` 也可以指定响应首部: ```ruby halt 402, {'Content-Type' => 'text/plain'}, 'revenge' ``` 当然也可以使用模板: ``` halt 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 ``` 只有当环境 (`RACK_ENV` 环境变量) 被设定为 `:production` 时才运行: ```ruby configure :production do ... end ``` 当环境被设定为 `:production` 或者 `:test` 时运行: ```ruby configure :production, :test do ... end ``` 你可以用 `settings` 访问这些配置项: ```ruby configure do set :foo, 'bar' end get '/' do settings.foo? # => true settings.foo # => 'bar' ... end ``` ### 配置攻击防护 Sinatra 使用 [Rack::Protection](https://github.com/sinatra/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['RACK_ENV'], 或者 "development" (如果 ENV['RACK_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"。 环境可以通过 `RACK_ENV` 环境变量设置。默认值为 "development"。 在开发环境下,每次请求都会重新加载所有模板, 特殊的 `not_found` 和 `error` 错误处理器会在浏览器中显示 stack trace。 在测试和生产环境下,模板默认会缓存。 在不同的环境下运行,设置 `RACK_ENV` 环境变量: ```shell RACK_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-1.4.8/CONTRIBUTING.md0000644000004100000410000001134613044044066015454 0ustar www-datawww-data# 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. **Update the change log (`CHANGELOG.md`):** The change log helps give an overview of the changes that go into each release, and gives credit where credit is due. We make sure that the change log is up to date before each release, and we always appreciate it when people make it easier to get the release out the door. 5. **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-book.gittr.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" sinatra-1.4.8/LICENSE0000644000004100000410000000216613044044066014230 0ustar www-datawww-dataCopyright (c) 2007, 2008, 2009 Blake Mizerany Copyright (c) 2010, 2011, 2012, 2013, 2014, 2015, 2016 Konstantin Haase Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. sinatra-1.4.8/README.de.md0000644000004100000410000023410713044044066015073 0ustar www-datawww-data# Sinatra *Wichtig: Dieses Dokument ist eine Übersetzung aus dem Englischen und unter Umständen nicht auf dem aktuellen Stand (aktuell Sinatra 1.4.5).* 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: Sinatra via `rubygems` installieren: ```shell gem install sinatra ``` Eine Datei mit dem Namen `myapp.rb` erstellen: ```ruby require 'sinatra' get '/' do 'Hallo Welt!' end ``` und im gleichen Verzeichnis ausführen: ```shell ruby myapp.rb ``` Die Seite kann nun unter [http://localhost:4567](http://localhost:4567) aufgerufen werden. ## Inhalt * [Sinatra](#sinatra) * [Routen](#routen) * [Bedingungen](#bedingungen) * [Rückgabewerte](#rckgabewerte) * [Eigene Routen-Muster](#eigene-routen-muster) * [Statische Dateien](#statische-dateien) * [Views/Templates](#viewstemplates) * [Direkte Templates](#direkte-templates) * [Verfügbare Templatesprachen](#verfgbare-templatesprachen) * [Haml Templates](#haml-templates) * [Erb Templates](#erb-templates) * [Builder Templates](#builder-templates) * [Nokogiri Templates](#nokogiri-templates) * [Sass Templates](#sass-templates) * [SCSS Templates](#scss-templates) * [Less Templates](#less-templates) * [Liquid Templates](#liquid-templates) * [Markdown Templates](#markdown-templates) * [Textile Templates](#textile-templates) * [RDoc Templates](#rdoc-templates) * [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-hinzufgen) * [Eigene Methoden zum Aufsuchen von Templates verwenden](#eigene-methoden-zum-aufsuchen-von-templates-verwenden) * [Filter](#filter) * [Helfer](#helfer) * [Sessions verwenden](#sessions-verwenden) * [Anhalten](#anhalten) * [Weiterspringen](#weiterspringen) * [Eine andere Route ansteuern](#eine-andere-route-ansteuern) * [Body, Status-Code und Header setzen](#body-status-code-und-header-setzen) * [Response-Streams](#response-streams) * [Logger](#logger) * [Mime-Types](#mime-types) * [URLs generieren](#urls-generieren) * [Browser-Umleitung](#browser-umleitung) * [Cache einsetzen](#cache-einsetzen) * [Dateien versenden](#dateien-versenden) * [Das Request-Objekt](#das-request-objekt) * [Anhänge](#anhnge) * [Umgang mit Datum und Zeit](#umgang-mit-datum-und-zeit) * [Nachschlagen von Template-Dateien](#nachschlagen-von-template-dateien) * [Konfiguration](#konfiguration) * [Einstellung des Angriffsschutzes](#einstellung-des-angriffsschutzes) * [Mögliche Einstellungen](#mgliche-einstellungen) * [Umgebungen](#umgebungen) * [Fehlerbehandlung](#fehlerbehandlung) * [Nicht gefunden](#nicht-gefunden) * [Fehler](#fehler) * [Rack-Middleware](#rack-middleware) * [Testen](#testen) * [Sinatra::Base - Middleware, Bibliotheken und modulare Anwendungen](#sinatrabase---middleware-bibliotheken-und-modulare-anwendungen) * [Modularer vs. klassischer Stil](#modularer-vs-klassischer-stil) * [Eine modulare Applikation bereitstellen](#eine-modulare-applikation-bereitstellen) * [Eine klassische Anwendung mit einer config.ru verwenden](#eine-klassische-anwendung-mit-einer-configru-verwenden) * [Wann sollte eine config.ru-Datei verwendet werden?](#wann-sollte-eine-configru-datei-verwendet-werden) * [Sinatra als Middleware nutzen](#sinatra-als-middleware-nutzen) * [Dynamische Applikationserstellung](#dynamische-applikationserstellung) * [Geltungsbereich und Bindung](#geltungsbereich-und-bindung) * [Anwendungs- oder Klassen-Scope](#anwendungs--oder-klassen-scope) * [Anfrage- oder Instanz-Scope](#anfrage--oder-instanz-scope) * [Delegation-Scope](#delegation-scope) * [Kommandozeile](#kommandozeile) * [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. 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 /\A\/hallo\/([\w]+)\z/ do "Hallo, #{params['captures'].first}!" end ``` Und auch hier können Block-Parameter genutzt werden: ```ruby get %r{/hallo/([\w]+)} do |c| "Hallo, #{c}!" end ``` Routen-Muster können auch mit optionalen Parametern ausgestattet werden: ```ruby get '/posts/:format?' do # passt auf "GET /posts/" sowie jegliche Erweiterung # wie "GET /posts/json", "GET /posts/xml" etc. end ``` Routen können auch den query-Parameter verwenden: ```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 ``` Anmerkung: Solange man den sog. Path Traversal Attack-Schutz nicht deaktiviert (siehe weiter unten), kann es sein, dass der Request-Pfad noch vor dem Abgleich mit den Routen modifiziert wird. ### Bedingungen An Routen können eine Vielzahl von Bedingungen geknüpft werden, die erfüllt sein müssen, damit der Block ausgeführt wird. Möglich wäre etwa eine Einschränkung des User-Agents über die interne Bedingung `:agent`: ```ruby get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do "Du verwendest Songbird Version #{params['agent'][0]}" end ``` Wird Songbird als Browser nicht verwendet, springt Sinatra zur nächsten Route: ```ruby get '/foo' do # passt auf andere Browser end ``` Andere mitgelieferte Bedingungen sind `:host_name` und `:provides`: ```ruby get '/', :host_name => /^admin\./ do "Adminbereich, Zugriff verweigert!" end get '/', :provides => 'html' do haml :index end get '/', :provides => ['rss', 'atom', 'xml'] do builder :feed end ``` `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 (Fixnum), Headers (Hash), Response-Body (antwortet auf #each)]`. * Ein Array mit zwei Elementen: `[Status (Fixnum), Response-Body (antwortet auf #each)]`. * Ein Objekt, das auf `#each` antwortet und den an diese Methode übergebenen Block nur mit Strings als Übergabewerte aufruft. * Ein Fixnum, das den Status-Code festlegt. Damit lässt sich relativ einfach Streaming implementieren: ```ruby class Stream def each 100.times { |i| yield "#{i}\n" } end end get('/') { Stream.new } ``` Ebenso kann die `stream`-Helfer-Methode (s.u.) verwendet werden, die Streaming direkt in die Route integriert. ### Eigene Routen-Muster Wie oben schon beschrieben, ist Sinatra von Haus aus mit Unterstützung für String-Muster und Reguläre Ausdrücke zum Abgleichen von Routen ausgestattet. Das muss aber noch nicht alles sein, es können ohne großen Aufwand eigene Routen-Muster erstellt werden: ```ruby class AllButPattern Match = Struct.new(:captures) def initialize(except) @except = except @captures = Match.new([]) end def match(str) @captures unless @except === str end end def all_but(pattern) AllButPattern.new(pattern) end get all_but("/index") do # ... end ``` Beachte, dass das obige Beispiel etwas übertrieben wirkt. Es geht auch einfacher: ```ruby get // do pass if request.path_info == "/index" # ... end ``` Oder unter Verwendung eines negativen look ahead: ```ruby get %r{^(?!/index$)} do # ... end ``` ## Statische Dateien Statische Dateien werden im `./public`-Ordner erwartet. Es ist möglich, einen anderen Ort zu definieren, indem man die `:public_folder`-Option setzt: ```ruby set :public_folder, File.dirname(__FILE__) + '/static' ``` Zu beachten ist, dass der Ordnername `public` nicht Teil der URL ist. Die Datei `./public/css/style.css` ist unter `http://example.com/css/style.css` zu finden. Um den `Cache-Control`-Header mit Informationen zu versorgen, verwendet man die `:static_cache_control`-Einstellung (s.u.). ## Views/Templates Alle Templatesprachen verwenden ihre eigene Renderingmethode, die jeweils einen String zurückgibt: ```ruby get '/' do erb :index end ``` Dieses Beispiel rendert `views/index.erb`. Anstelle eines Templatenamens kann man auch direkt die Templatesprache verwenden: ```ruby get '/' do code = "<%= Time.now %>" erb code end ``` Templates nehmen ein zweite Argument an, den Options-Hash: ```ruby get '/' do erb :index, :layout => :post end ``` Dieses Beispiel rendert `views/index.erb` eingebettet in `views/post.erb` (Voreinstellung ist `views/layout.erb`, sofern es vorhanden ist.) Optionen, die Sinatra nicht versteht, werden an das Template weitergereicht: ```ruby get '/' do haml :index, :format => :html5 end ``` Für alle Templates können auch Einstellungen, die für alle Routen gelten, festgelegt werden: ```ruby set :haml, :format => :html5 get '/' do haml :index end ``` Optionen, die an die Rendermethode weitergegeben werden, überschreiben die Einstellungen, die mit `set` festgelegt wurden. Einstellungen:
locals
Liste von lokalen Variablen, die an das Dokument weitergegeben werden. Praktisch für Partials: erb "<%= foo %>", :locals => {:foo => "bar"}
default_encoding
Gibt die Stringkodierung an, die verwendet werden soll. Voreingestellt auf settings.default_encoding.
views
Ordner, aus dem die Templates geladen werden. Voreingestellt auf settings.views.
layout
Legt fest, ob ein Layouttemplate verwendet werden soll oder nicht (true oderfalse). Ist es ein Symbol, dann legt es fest, welches Template als Layout verwendet wird: erb :index, :layout => !request.xhr?
content_type
Content-Typ den das Template ausgibt. Voreinstellung hängt von der Templatesprache ab.
scope
Scope, in dem das Template gerendert wird. Liegt standardmäßig innerhalb der App-Instanz. Wird Scope geändert, sind Instanzvariablen und Helfermethoden nicht verfügbar.
layout_engine
Legt fest, welcher Renderer für das Layout verantwortlich ist. Hilfreich für Sprachen, die sonst keine Templates unterstützen. Voreingestellt auf den Renderer, der für das Template verwendet wird: set :rdoc, :layout_engine => :erb
layout_options
Besondere Einstellungen, die nur für das Rendering verwendet werden: set :rdoc, :layout_options => { :views => 'views/layouts' }
Sinatra geht davon aus, dass die Templates sich im `./views` Verzeichnis befinden. Es kann jedoch ein anderer Ordner festgelegt werden: ```ruby set :views, settings.root + '/templates' ``` Es ist zu beachten, dass immer mit Symbolen auf Templates verwiesen werden muss, auch dann, wenn sie sich in einem Unterordner befinden: ```ruby haml :'unterverzeichnis/template' ``` Rendering-Methoden rendern jeden String direkt. ### Direkte Templates ```ruby get '/' do haml '%div.title Hallo Welt' end ``` Hier wird der String direkt gerendert. ### Verfügbare Templatesprachen Einige Sprachen haben mehrere Implementierungen. Um festzulegen, welche verwendet wird (und dann auch Thread-sicher ist), verwendet man am besten zu Beginn ein `'require'`: ```ruby require 'rdiscount' # oder require 'bluecloth' get('/') { markdown :index } ``` #### Haml Templates
Abhängigkeit haml
Dateierweiterung .haml
Beispiel haml :index, :format => :html5
#### Erb Templates
Abhängigkeit erubis oder erb (Standardbibliothek von Ruby)
Dateierweiterungen .erb, .rhtml oder .erubis (nur Erubis)
Beispiel erb :index
#### Builder Templates
Abhängigkeit builder
Dateierweiterung .builder
Beispiel builder { |xml| xml.em "Hallo" }
Nimmt ebenso einen Block für Inline-Templates entgegen (siehe Beispiel). #### Nokogiri Templates
Abhängigkeit nokogiri
Dateierweiterung .nokogiri
Beispiel nokogiri { |xml| xml.em "Hallo" }
Nimmt ebenso einen Block für Inline-Templates entgegen (siehe Beispiel). #### Sass Templates
Abhängigkeit sass
Dateierweiterung .sass
Beispiel sass :stylesheet, :style => :expanded
#### SCSS Templates
Abhängigkeit sass
Dateierweiterung .scss
Beispiel scss :stylesheet, :style => :expanded
#### Less Templates
Abhängigkeit less
Dateierweiterung .less
Beispiel less :stylesheet
#### Liquid Templates
Abhängigkeit liquid
Dateierweiterung .liquid
Beispiel liquid :index, :locals => { :key => 'Wert' }
Da man aus dem Liquid-Template heraus keine Ruby-Methoden aufrufen kann (ausgenommen `yield`), wird man üblicherweise locals verwenden wollen, mit denen man Variablen weitergibt. #### Markdown Templates
Abhängigkeit Eine der folgenden Bibliotheken: RDiscount, RedCarpet, BlueCloth, kramdown oder maruku
Dateierweiterungen .markdown, .mkd und .md
Beispiel markdown :index, :layout_engine => :erb
Da man aus den Markdown-Templates heraus keine Ruby-Methoden aufrufen und auch keine locals verwenden kann, wird man Markdown üblicherweise in Kombination mit anderen Renderern verwenden wollen: ```ruby erb :overview, :locals => { :text => markdown(:einfuehrung) } ``` Beachte, dass man die `markdown`-Methode auch aus anderen Templates heraus aufrufen kann: ```ruby %h1 Gruß von Haml! %p= markdown(:Grüße) ``` Da man Ruby nicht von Markdown heraus aufrufen kann, können auch Layouts nicht in Markdown geschrieben werden. Es ist aber möglich, einen Renderer für die Templates zu verwenden und einen anderen für das Layout, indem die `:layout_engine`-Option verwendet wird. #### Textile Templates
Abhängigkeit RedCloth
Dateierweiterung .textile
Beispiel textile :index, :layout_engine => :erb
Da man aus dem Textile-Template heraus keine Ruby-Methoden aufrufen und auch keine locals verwenden kann, wird man Textile üblicherweise in Kombination mit anderen Renderern verwenden wollen: ```ruby erb :overview, :locals => { :text => textile(:einfuehrung) } ``` Beachte, dass man die `textile`-Methode auch aus anderen Templates heraus aufrufen kann: ```ruby %h1 Gruß von Haml! %p= textile(:Grüße) ``` Da man Ruby nicht von Textile heraus aufrufen kann, können auch Layouts nicht in Textile geschrieben werden. Es ist aber möglich, einen Renderer für die Templates zu verwenden und einen anderen für das Layout, indem die `:layout_engine`-Option verwendet wird. #### RDoc Templates
Abhängigkeit rdoc
Dateierweiterung .rdoc
Beispiel textile :README, :layout_engine => :erb
Da man aus dem RDoc-Template heraus keine Ruby-Methoden aufrufen und auch keine locals verwenden kann, wird man RDoc üblicherweise in Kombination mit anderen Renderern verwenden wollen: ```ruby erb :overview, :locals => { :text => rdoc(:einfuehrung) } ``` Beachte, dass man die `rdoc`-Methode auch aus anderen Templates heraus aufrufen kann: ```ruby %h1 Gruß von Haml! %p= rdoc(:Grüße) ``` Da man Ruby nicht von RDoc heraus aufrufen kann, können auch Layouts nicht in RDoc geschrieben werden. Es ist aber möglich, einen Renderer für die Templates zu verwenden und einen anderen für das Layout, indem die `:layout_engine`-Option verwendet wird. #### 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 anderen Dateien aufzurufen, muss explizit `enable :inline_templates` verwendet werden. ### Benannte Templates Templates können auch mit der Top-Level `template`-Methode definiert werden: ```ruby template :layout do "%html\n =yield\n" end template :index do '%div.title Hallo Welt!' end get '/' do haml :index end ``` Wenn ein Template mit dem Namen "layout" existiert, wird es bei jedem Aufruf verwendet. Durch `:layout => false` kann das Ausführen verhindert werden: ```ruby get '/' do haml :index, :layout => !request.xhr? # !request.xhr? prüft, ob es sich um einen asynchronen Request handelt. # wenn nicht, dann verwende ein Layout (negiert durch !) end ``` ### Dateiendungen zuordnen Um eine Dateiendung einer Template-Engine zuzuordnen, kann `Tilt.register` genutzt werden. Wenn etwa die Dateiendung `tt` für Textile-Templates genutzt werden soll, lässt sich dies wie folgt bewerkstelligen: ```ruby Tilt.register :tt, Tilt[:textile] ``` ### Eine eigene Template-Engine hinzufügen Zu allererst muss die Engine bei Tilt registriert und danach eine Rendering-Methode erstellt werden: ```ruby Tilt.register :mtt, MeineTolleTemplateEngine helpers do def mtt(*args) render(:mtt, *args) end end get '/' do mtt :index end ``` Dieser Code rendert `./views/application.mtt`. Siehe [github.com/rtomayko/tilt](https://github.com/rtomayko/tilt), um mehr über Tilt zu erfahren. ### 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 ``` Filter können optional auch mit einem Muster ausgestattet werden, das auf den Request-Pfad passen muss, damit der Filter ausgeführt wird: ```ruby before '/protected/*' do authenticate! end after '/create/:slug' do |slug| session[:last_slug] = slug end ``` Ähnlich wie Routen können Filter auch mit weiteren Bedingungen eingeschränkt werden: ```ruby before :agent => /Songbird/ do # ... end after '/blog/*', :host_name => 'example.com' do # ... end ``` ## Helfer Durch die Top-Level `helpers`-Methode werden sogenannte Helfer-Methoden definiert, die in Routen und Templates verwendet werden können: ```ruby helpers do def bar(name) "#{name}bar" end end get '/:name' do bar(params['name']) end ``` ### Sessions verwenden Sessions werden verwendet, um Zustände zwischen den Requests zu speichern. Sind sie aktiviert, kann ein Session-Hash je Benutzer-Session verwendet werden: ```ruby enable :sessions get '/' do "value = " << session[:value].inspect end get '/:value' do session[:value] = params['value'] end ``` Beachte, dass `enable :sessions` alle Daten in einem Cookie speichert. Unter Umständen kann dies negative Effekte haben, z.B. verursachen viele Daten höheren, teilweise überflüssigen Traffic. Um das zu vermeiden, kann eine Rack- Session-Middleware verwendet werden. Dabei wird auf `enable :sessions` verzichtet und die Middleware wie üblich im Programm eingebunden: ```ruby use Rack::Session::Pool, :expire_after => 2592000 get '/' do "value = " << session[:value].inspect end get '/:value' do session[:value] = params['value'] end ``` Um die Sicherheit zu erhöhen, werden Cookies, die Session-Daten führen, mit einem sogenannten Session-Secret signiert. Da sich dieses Geheimwort bei jedem Neustart der Applikation automatisch ändert, ist es sinnvoll, ein eigenes zu wählen, damit sich alle Instanzen der Applikation dasselbe Session-Secret teilen: ```ruby set :session_secret, 'super_geheimes_Gegeimnis' ``` Zur weiteren Konfiguration kann man einen Hash mit Optionen in den `sessions` Einstellungen ablegen. ```ruby set :sessions, :domain => 'foo.com' ``` Um eine Session mit anderen Apps und zwischen verschiedenen Subdomains von foo.com zu teilen, wird ein *.* der Domain vorangestellt: ```ruby set :sessions, :domain => '.foo,com' ``` ### Anhalten Zum sofortigen Stoppen eines Request in einem Filter oder einer Route: ```ruby halt ``` Der Status kann beim Stoppen mit angegeben werden: ```ruby halt 410 ``` Oder auch den Response-Body: ```ruby halt 'Hier steht der Body' ``` Oder beides: ```ruby halt 401, 'verschwinde!' ``` Sogar mit Headern: ```ruby halt 402, {'Content-Type' => 'text/plain'}, 'Rache' ``` Natürlich ist es auch möglich, ein Template mit `halt` zu verwenden: ```ruby halt erb(:error) ``` ### Weiterspringen Eine Route kann mittels `pass` zu der nächsten passenden Route springen: ```ruby get '/raten/:wer' do pass unless params['wer'] == 'Frank' 'Du hast mich!' end get '/raten/*' do 'Du hast mich nicht!' end ``` Der Block wird sofort verlassen und es wird nach der nächsten treffenden Route gesucht. Ein 404-Fehler wird zurückgegeben, wenn kein treffendes Routen-Muster gefunden wird. ### Eine andere Route ansteuern Wenn nicht zu einer anderen Route gesprungen werden soll, sondern nur das Ergebnis einer anderen Route gefordert wird, kann `call` für einen internen Request verwendet werden: ```ruby get '/foo' do status, headers, body = call env.merge("PATH_INFO" => '/bar') [status, headers, body.map(&:upcase)] end get '/bar' do "bar" end ``` Beachte, dass in dem oben angegeben Beispiel die Performance erheblich erhöht werden kann, wenn `"bar"` in eine Helfer-Methode umgewandelt wird, auf die `/foo` und `/bar` zugreifen können. Wenn der Request innerhalb derselben Applikations-Instanz aufgerufen und keine Kopie der Instanz erzeugt werden soll, kann `call!` anstelle von `call` verwendet werden. ### Body, Status-Code und Header setzen Es ist möglich und empfohlen, den Status-Code sowie den Response-Body mit einem Returnwert in der Route zu setzen. In manchen Situationen kann es jedoch sein, dass der Body an anderer Stelle während der Ausführung gesetzt werden soll. Dafür kann man die Helfer-Methode `body` einsetzen. Ist sie gesetzt, kann sie zu einem späteren Zeitpunkt aufgerufen werden: ```ruby get '/foo' do body "bar" end after do puts body end ``` Ebenso ist es möglich, einen Block an `body` weiterzureichen, der dann vom Rack-Handler ausgeführt wird (lässt sich z.B. zur Umsetzung von Streaming einsetzen, siehe auch "Rückgabewerte"). Vergleichbar mit `body` lassen sich auch Status-Code und Header setzen: ```ruby get '/foo' do status 418 headers \ "Allow" => "BREW, POST, GET, PROPFIND, WHEN", "Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt" halt "Ich bin ein Teekesselchen" end ``` Genau wie bei `body` liest ein Aufrufen von `headers` oder `status` ohne Argumente den aktuellen Wert aus. ### Response-Streams In manchen Situationen sollen Daten bereits an den Client zurückgeschickt werden, bevor ein vollständiger Response bereit steht. Manchmal will man die Verbindung auch erst dann beenden und Daten so lange an den Client zurückschicken, bis er die Verbindung abbricht. Für diese Fälle gibt es die `stream`-Helfer-Methode, die es einem erspart eigene Lösungen zu schreiben: ```ruby get '/' do stream do |out| out << "Das ist ja mal wieder fanta -\n" sleep 0.5 out << " (bitte warten…) \n" sleep 1 out << "- stisch!\n" end end ``` Damit lassen sich Streaming-APIs realisieren, sog. [Server Sent Events](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 ``` ### 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.
Soweit vom Rack-Handler unterstützt, werden neben der Übertragung über den Ruby-Prozess auch andere Möglichkeiten genutzt. Bei Verwendung der `send_file`-Helfer-Methode kümmert sich Sinatra selbstständig um die Range-Requests. ### Das Request-Objekt Auf das `request`-Objekt der eigehenden Anfrage kann vom Anfrage-Scope aus zugegriffen werden: ```ruby # App läuft unter http://example.com/example get '/foo' do t = %w[text/css text/html application/javascript] request.accept # ['text/html', '*/*'] request.accept? 'text/xml' # true request.preferred_type(t) # 'text/html' request.body # Request-Body des Client (siehe unten) request.scheme # "http" request.script_name # "/example" request.path_info # "/foo" request.port # 80 request.request_method # "GET" request.query_string # "" request.content_length # Länge des request.body request.media_type # Medientypus von request.body request.host # "example.com" request.get? # true (ähnliche Methoden für andere Verben) request.form_data? # false request["irgendein_param"] # Wert von einem Parameter; [] ist die Kurzform für den params Hash request.referrer # Der Referrer des Clients oder '/' request.user_agent # User-Agent (verwendet in der :agent Bedingung) request.cookies # Hash des Browser-Cookies request.xhr? # Ist das hier ein Ajax-Request? request.url # "http://example.com/example/foo" request.path # "/example/foo" request.ip # IP-Adresse des Clients request.secure? # false (true wenn SSL) request.forwarded? # true (Wenn es hinter einem Reverse-Proxy verwendet wird) request.env # vollständiger env-Hash von Rack übergeben end ``` Manche Optionen, wie etwa `script_name` oder `path_info`, sind auch schreibbar: ```ruby before { request.path_info = "/" } get "/" do "Alle Anfragen kommen hier an!" end ``` Der `request.body` ist ein IO- oder StringIO-Objekt: ```ruby post "/api" do request.body.rewind # falls schon jemand davon gelesen hat daten = JSON.parse request.body.read "Hallo #{daten['name']}!" end ``` ### Anhänge Damit der Browser erkennt, dass ein Response gespeichert und nicht im Browser angezeigt werden soll, kann der `attachment`-Helfer verwendet werden: ```ruby get '/' do attachment "Speichern!" end ``` Ebenso kann eine Dateiname als Parameter hinzugefügt werden: ```ruby get '/' do attachment "info.txt" "Speichern!" end ``` ### Umgang mit Datum und Zeit Sinatra bietet eine `time_for`-Helfer-Methode, die aus einem gegebenen Wert ein Time-Objekt generiert. Ebenso kann sie nach `DateTime`, `Date` und ähnliche Klassen konvertieren: ```ruby get '/' do pass if Time.now > time_for('Dec 23, 2012') "noch Zeit" end ``` Diese Methode wird intern für +expires, `last_modiefied` und ihresgleichen verwendet. Mit ein paar Handgriffen lässt sich diese Methode also in ihrem Verhalten erweitern, indem man `time_for` in der eigenen Applikation überschreibt: ```ruby helpers do def time_for(value) case value when :yesterday then Time.now - 24*60*60 when :tomorrow then Time.now + 24*60*60 else super end end end get '/' do last_modified :yesterday expires :tomorrow "Hallo" end ``` ### Nachschlagen von Template-Dateien Die `find_template`-Helfer-Methode wird genutzt, um Template-Dateien zum Rendern aufzufinden: ```ruby find_template settings.views, 'foo', Tilt[:haml] do |file| puts "könnte diese hier sein: #{file}" end ``` Das ist zwar nicht wirklich brauchbar, aber wenn man sie überschreibt, kann sie nützlich werden, um eigene Nachschlage-Mechanismen einzubauen. Zum Beispiel dann, wenn mehr als nur ein view-Verzeichnis verwendet werden soll: ```ruby set :views, ['views', 'templates'] helpers do def find_template(views, name, engine, &block) Array(views).each { |v| super(v, name, engine, &block) } end end ``` Ein anderes Beispiel wäre, verschiedene Vereichnisse für verschiedene Engines zu verwenden: ```ruby set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views' helpers do def find_template(views, name, engine, &block) _, folder = views.detect { |k,v| engine == Tilt[k] } folder ||= views[:default] super(folder, name, engine, &block) end end ``` Ebensogut könnte eine Extension aber auch geschrieben und mit anderen geteilt werden! Beachte, dass `find_template` nicht prüft, ob eine Datei tatsächlich existiert. Es wird lediglich der angegebene Block aufgerufen und nach allen möglichen Pfaden gesucht. Das ergibt kein Performance-Problem, da `render` `block` verwendet, sobald eine Datei gefunden wurde. Ebenso werden Template-Pfade samt Inhalt gecached, solange nicht im Entwicklungsmodus gearbeitet wird. Das sollte im Hinterkopf behalten werden, wenn irgendwelche verrückten Methoden zusammenbastelt werden. ### Konfiguration Wird einmal beim Starten in jedweder Umgebung ausgeführt: ```ruby configure do # setze eine Option set :option, 'wert' # setze mehrere Optionen set :a => 1, :b => 2 # das gleiche wie `set :option, true` enable :option # das gleiche wie `set :option, false` disable :option # dynamische Einstellungen mit Blöcken set(:css_dir) { File.join(views, 'css') } end ``` Läuft nur, wenn die Umgebung (RACK_ENV-Umgebungsvariable) auf `:production` gesetzt ist: ```ruby configure :production do ... end ``` Läuft nur, wenn die Umgebung auf `:production` oder auf `:test` gesetzt ist: ```ruby configure :production, :test do ... end ``` Diese Einstellungen sind über `settings` erreichbar: ```ruby configure do set :foo, 'bar' end get '/' do settings.foo? # => true settings.foo # => 'bar' ... end ``` #### Einstellung des Angriffsschutzes Sinatra verwendet [Rack::Protection](https://github.com/sinatra/rack-protection#readme), um die Anwendung vor häufig vorkommenden Angriffen zu schützen. Diese Voreinstellung lässt sich selbstverständlich deaktivieren, der damit verbundene Geschwindigkeitszuwachs steht aber in keinem Verhätnis zu den möglichen Risiken. ```ruby disable :protection ``` Um einen bestimmten Schutzmechanismus zu deaktivieren, fügt man `protection` einen Hash mit Optionen hinzu: ```ruby set :protection, :except => :path_traversal ``` Neben Strings akzeptiert `:except` auch Arrays, um gleich mehrere Schutzmechanismen zu deaktivieren: ```ruby set :protection, :except => [:path_traversal, :session_hijacking] ``` #### Mögliche Einstellungen
absolute_redirects
Wenn ausgeschaltet, wird Sinatra relative Redirects zulassen. Jedoch ist Sinatra dann nicht mehr mit RFC 2616 (HTTP 1.1) konform, das nur absolute Redirects zulässt. Sollte eingeschaltet werden, wenn die Applikation hinter einem Reverse-Proxy liegt, der nicht ordentlich eingerichtet ist. Beachte, dass die url-Helfer-Methode nach wie vor absolute URLs erstellen wird, es sei denn, es wird als zweiter Parameter false angegeben. Standardmäßig nicht aktiviert.
add_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 content_type oder "development" eingestellt, soweit ersteres nicht vorhanden.
logging
Den Logger verwenden.
lock
Jeder Request wird gelocked. Es kann nur ein Request pro Ruby-Prozess gleichzeitig verarbeitet werden. Eingeschaltet, wenn die Applikation threadsicher ist. Standardmäßig nicht aktiviert.
method_override
Verwende _method, um put/delete-Formulardaten in Browsern zu verwenden, die dies normalerweise nicht unterstützen.
port
Port für die Applikation. Wird nur im internen Server verwendet.
prefixed_redirects
Entscheidet, ob request.script_name in Redirects eingefügt wird oder nicht, wenn kein absoluter Pfad angegeben ist. Auf diese Weise verhält sich redirect '/foo' so, als wäre es ein redirect to('/foo'). Standardmäßig nicht aktiviert.
protection
Legt fest, ob der Schutzmechanismus für häufig Vorkommende Webangriffe auf Webapplikationen aktiviert wird oder nicht. Weitere Informationen im vorhergehenden Abschnitt.
public_folder
Das öffentliche Verzeichnis, aus dem Daten zur Verfügung gestellt werden können. Wird nur dann verwendet, wenn statische Daten zur Verfügung gestellt werden können (s.u. static Option). Leitet sich von der app_file Einstellung ab, wenn nicht gesetzt.
public_dir
Alias für public_folder, s.o.
reload_templates
Im development-Modus aktiviert.
root
Wurzelverzeichnis des Projekts. Leitet sich von der app_file Einstellung ab, wenn nicht gesetzt.
raise_errors
Einen Ausnahmezustand aufrufen. Beendet die Applikation. Ist automatisch aktiviert, wenn die Umgebung auf "test" eingestellt ist. Ansonsten ist diese Option deaktiviert.
run
Wenn aktiviert, wird Sinatra versuchen, den Webserver zu starten. Nicht verwenden, wenn Rackup oder anderes verwendet werden soll.
running
Läuft der eingebaute Server? Diese Einstellung nicht ändern!
server
Server oder Liste von Servern, die als eingebaute Server zur Verfügung stehen. Die Reihenfolge gibt die Priorität vor, die Voreinstellung hängt von der verwendenten Ruby Implementierung ab.
sessions
Sessions auf Cookiebasis mittels Rack::Session::Cookieaktivieren. Für weitere Infos bitte in der Sektion ‘Sessions verwenden’ nachschauen.
show_exceptions
Bei Fehlern einen Stacktrace im Browseranzeigen. Ist automatisch aktiviert, wenn die Umgebung auf "development" eingestellt ist. Ansonsten ist diese Option deaktiviert. Kann auch auf :after_handler gestellt werden, um eine anwendungsspezifische Fehlerbehandlung auszulösen, bevor der Fehlerverlauf im Browser angezeigt wird.
static
Entscheidet, ob Sinatra statische Dateien zur Verfügung stellen soll oder nicht. Sollte nicht aktiviert werden, wenn ein Server verwendet wird, der dies auch selbstständig erledigen kann. Deaktivieren wird die Performance erhöhen. Standardmäßig aktiviert.
static_cache_control
Wenn Sinatra statische Daten zur Verfügung stellt, können mit dieser Einstellung die Cache-Control Header zu den Responses hinzugefügt werden. Die Einstellung verwendet dazu die cache_control Helfer-Methode. Standardmäßig deaktiviert. Ein Array wird verwendet, um mehrere Werte gleichzeitig zu übergeben: set :static_cache_control, [:public, :max_age => 300]
threaded
Wird es auf true gesetzt, wird Thin aufgefordert EventMachine.defer zur Verarbeitung des Requests einzusetzen.
traps
Einstellung, Sinatra 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 `RACK_ENV` Umgebungsvariable gesetzt werden. Die Standardeinstellung ist `"development"`. In diesem Modus werden alle Templates zwischen Requests neu geladen. Dazu gibt es besondere Fehlerseiten für 404 Stati und Fehlermeldungen. In `"production"` und `"test"` werden Templates automatisch gecached. Um die Anwendung in einer anderen Umgebung auszuführen kann man die `-e` Option verwenden: ```shell ruby my_app.rb -e [ENVIRONMENT] ``` In der Anwendung kann man die die Methoden `development?`, `test?` und `production?` verwenden, um die aktuelle Umgebung zu erfahren. ## Fehlerbehandlung Error-Handler laufen in demselben Kontext wie Routen und Filter, was bedeutet, dass alle Goodies wie `haml`, `erb`, `halt`, etc. verwendet werden können. ### Nicht gefunden Wenn eine `Sinatra::NotFound`-Exception geworfen wird oder der Statuscode 404 ist, wird der `not_found`-Handler ausgeführt: ```ruby not_found do 'Seite kann nirgendwo gefunden werden.' end ``` ### Fehler Der `error`-Handler wird immer ausgeführt, wenn eine Exception in einem Routen-Block oder in einem Filter geworfen wurde. 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/), 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_rack_env get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' assert_equal "Du verwendest Songbird!", last_response.body end end ``` Hinweis: Wird Sinatra modular verwendet, muss Sinatra::Application mit dem Namen der Applikations-Klasse ersetzt werden. ## Sinatra::Base - Middleware, Bibliotheken und modulare Anwendungen Das Definieren einer Top-Level-Anwendung funktioniert gut für Mikro-Anwendungen, hat aber Nachteile, wenn wiederverwendbare Komponenten wie Middleware, Rails Metal, einfache Bibliotheken mit Server-Komponenten oder auch Sinatra-Erweiterungen geschrieben werden sollen. Das Top-Level geht von einer Konfiguration für eine Mikro-Anwendung aus (wie sie z.B. bei einer einzelnen Anwendungsdatei, `./public` und `./views` Ordner, Logging, Exception-Detail-Seite, usw.). Genau hier kommt `Sinatra::Base` ins Spiel: ```ruby require 'sinatra/base' class MyApp < Sinatra::Base set :sessions, true set :foo, 'bar' get '/' do 'Hallo Welt!' end end ``` Die MyApp-Klasse ist eine unabhängige Rack-Komponente, die als Middleware, Endpunkt oder via Rails Metal verwendet werden kann. Verwendet wird sie durch `use` oder `run` von einer Rackup-`config.ru`-Datei oder als Server-Komponente einer Bibliothek: ```ruby MyApp.run! :host => 'localhost', :port => 9090 ``` Die Methoden der `Sinatra::Base`-Subklasse sind genau dieselben wie die der Top-Level-DSL. Die meisten Top-Level-Anwendungen können mit nur zwei Veränderungen zu `Sinatra::Base` konvertiert werden: * Die Datei sollte `require 'sinatra/base'` anstelle von `require 'sinatra/base'` aufrufen, ansonsten werden alle von Sinatras DSL-Methoden in den Top-Level-Namespace importiert. * Alle Routen, Error-Handler, Filter und Optionen der Applikation müssen in einer Subklasse von `Sinatra::Base` definiert werden. `Sinatra::Base` ist ein unbeschriebenes Blatt. Die meisten Optionen sind per Standard deaktiviert. Das betrifft auch den eingebauten Server. Siehe [Optionen und Konfiguration](http://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/Ramaze/Camping/...): ```ruby require 'sinatra/base' class LoginScreen < Sinatra::Base enable :sessions get('/login') { haml :login } post('/login') do if params['name'] == 'admin' && params['password'] == 'admin' session['user_name'] = params['name'] else redirect '/login' end end end class MyApp < Sinatra::Base # Middleware wird vor Filtern ausgeführt use LoginScreen before do unless session['user_name'] halt "Zugriff verweigert, bitte einloggen." end end get('/') { "Hallo #{session['user_name']}." } end ``` ### Dynamische Applikationserstellung Manche Situationen erfordern die Erstellung neuer Applikationen zur Laufzeit, ohne dass sie einer Konstanten zugeordnet werden. Dies lässt sich mit `Sinatra.new` erreichen: ```ruby require 'sinatra/base' my_app = Sinatra.new { get('/') { "hallo" } } my_app.run! ``` Die Applikation kann mit Hilfe eines optionalen Parameters erstellt werden: ```ruby # config.ru require 'sinatra/base' controller = Sinatra.new do enable :logging helpers MyHelpers end map('/a') do run Sinatra.new(controller) { get('/') { 'a' } } end map('/b') do run Sinatra.new(controller) { get('/') { 'b' } } end ``` Das ist besonders dann interessant, wenn Sinatra-Erweiterungen getestet werden oder Sinatra in einer Bibliothek Verwendung findet. Ebenso lassen sich damit hervorragend Sinatra-Middlewares erstellen: ```ruby require 'sinatra/base' use Sinatra do get('/') { ... } end run RailsProject::Application ``` ## Geltungsbereich und Bindung Der Geltungsbereich (Scope) legt fest, welche Methoden und Variablen zur Verfügung stehen. ### Anwendungs- oder Klassen-Scope Jede Sinatra-Anwendung entspricht einer `Sinatra::Base`-Subklasse. Falls die Top- Level-DSL verwendet wird (`require 'sinatra'`), handelt es sich um `Sinatra::Application`, andernfalls ist es jene Subklasse, die explizit angelegt wurde. Auf Klassenebene stehen Methoden wie `get` oder `before` zur Verfügung, es gibt aber keinen Zugriff auf das `request`-Object oder die `session`, da nur eine einzige Klasse für alle eingehenden Anfragen genutzt wird. Optionen, die via `set` gesetzt werden, sind Methoden auf Klassenebene: ```ruby class MyApp < Sinatra::Base # Hey, ich bin im Anwendungsscope! set :foo, 42 foo # => 42 get '/foo' do # Hey, ich bin nicht mehr im Anwendungs-Scope! end end ``` Im Anwendungs-Scope befindet man sich: * In der Anwendungs-Klasse. * In Methoden, die von Erweiterungen definiert werden. * Im Block, der an `helpers` übergeben wird. * In Procs und Blöcken, die an `set` übergeben werden. * Der an `Sinatra.new` übergebene Block Auf das Scope-Objekt (die Klasse) kann wie folgt zugegriffen werden: * Über das Objekt, das an den `configure`-Block übergeben wird (`configure { |c| ... }`). * `settings` aus den anderen Scopes heraus. ### Anfrage- oder Instanz-Scope Für jede eingehende Anfrage wird eine neue Instanz der Anwendungs-Klasse erstellt und alle Handler in diesem Scope ausgeführt. Aus diesem Scope heraus kann auf `request` oder `session` zugegriffen und Methoden wie `erb` oder `haml` aufgerufen werden. Außerdem kann mit der `settings`-Method auf den Anwendungs-Scope zugegriffen werden: ```ruby class MyApp < Sinatra::Base # Hey, ich bin im Anwendungs-Scope! get '/neue_route/:name' do # Anfrage-Scope für '/neue_route/:name' @value = 42 settings.get "/#{params['name']}" do # Anfrage-Scope für "/#{params['name']}" @value # => nil (nicht dieselbe Anfrage) end "Route definiert!" end end ``` Im Anfrage-Scope befindet man sich: * In get, head, post, put, delete, options, patch, link und unlink Blöcken * In before und after Filtern * In Helfer-Methoden * In Templates ### Delegation-Scope Vom Delegation-Scope aus werden Methoden einfach an den Klassen-Scope weitergeleitet. Dieser verhält sich jedoch nicht 100%ig wie der Klassen-Scope, da man nicht die Bindung der Klasse besitzt: Nur Methoden, die explizit als delegierbar markiert wurden, stehen hier zur Verfügung und es kann nicht auf die Variablen des Klassenscopes zugegriffen werden (mit anderen Worten: es gibt ein anderes `self`). Weitere Delegationen können mit `Sinatra::Delegator.delegate :methoden_name` hinzugefügt werden. Im Delegation-Scop befindet man sich: * Im Top-Level, wenn `require 'sinatra'` aufgerufen wurde. * In einem Objekt, das mit dem `Sinatra::Delegator`-Mixin erweitert wurde. Schau am besten im Code nach: Hier ist [Sinatra::Delegator mixin](http://github.com/sinatra/sinatra/blob/master/lib/sinatra/base.rb#L1064 ) definiert und wird in den [globalen Namespace eingebunden](http://github.com/sinatra/sinatra/blob/master/lib/sinatra/main.rb ## Kommandozeile Sinatra-Anwendungen können direkt von der Kommandozeile aus gestartet werden: ```shell ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-h HOST] [-s HANDLER] ``` Die Optionen sind: ``` -h # Hilfe -p # Port setzen (Standard ist 4567) -h # Host setzen (Standard ist 0.0.0.0) -e # Umgebung setzen (Standard ist development) -s # Rack-Server/Handler setzen (Standard ist thin) -x # Mutex-Lock einschalten (Standard ist off) ``` ### 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 1.8.7
1.8.7 wird vollständig unterstützt, ein Wechsel zu JRuby oder Rubinius wird aber empfohlen. Ruby 1.8.7 wird noch bis Sinatra 2.0 unterstützt werden. Frühere Versionen von Ruby sind nicht kompatibel mit Sinatra.
Ruby 1.9.2
1.9.2 wird mindestens bis Sinatra 1.5 voll unterstützt. Version 1.9.2p0 sollte nicht verwendet werden, da unter Sinatra immer wieder Segfaults auftreten.
Ruby 1.9.3
1.9.3 wird vollständig unterstützt und empfohlen. Achtung, bei einem Upgrade von einer früheren Version von Ruby zu Ruby 1.9.3 werden alle Sessions ungültig. Ruby 1.9.3 wird bis Sinatra 2.0 unterstützt werden.
Ruby 2.x
2.x wird vollständig unterstützt.
Rubinius
Rubinius (Version >= 2.x) wird offiziell unterstützt. Es wird empfohlen, den Puma Server zu installieren (gem install puma )
JRuby
Aktuelle JRuby Versionen werden offiziell unterstützt. Es wird empfohlen, keine C-Erweiterungen zu verwenden und als Server Trinidad zu verwenden (gem install trinidad).
Die nachfolgend aufgeführten Ruby-Implementierungen werden offiziell nicht von Sinatra unterstützt, funktionieren aber normalerweise: * Ruby Enterprise Edition * Ältere Versionen von JRuby und Rubinius * MacRuby (gem install control_tower wird empfohlen), Maglev, IronRuby * Ruby 1.9.0 und 1.9.1 Nicht offiziell unterstützt bedeutet, dass wenn Sachen nicht funktionieren, wir davon ausgehen, dass es nicht an Sinatra sondern an der jeweiligen Implementierung liegt. Im Rahmen unserer CI (Kontinuierlichen Integration) wird bereits ruby-head (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, dass einen funktionierenden Ruby-Interpreter aufweist. Sinatra läuft aktuell nicht unter Cardinal, SmallRuby, BlueRuby oder Ruby <= 1.8.7. ## Der neuste Stand (The Bleeding Edge) Um auf dem neusten Stand zu bleiben, kann der Master-Branch verwendet werden. Er sollte recht stabil sein. Ebenso gibt es von Zeit zu Zeit prerelease Gems, die so installiert werden: ```shell gem install sinatra --pre ``` ### Mit Bundler Wenn die Applikation mit der neuesten Version von Sinatra und [Bundler](http://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... gem 'activerecord', '~> 3.0' # ...oder ActiveRecord 3.x ``` Beachte: Hier sollten alle Abhängigkeiten eingetragen werden. Sinatras eigene, direkte Abhängigkeiten (Tilt und Rack) werden von Bundler automatisch aus dem Gemfile von Sinatra hinzugefügt. Jetzt kannst du deine Applikation starten: ```shell bundle exec ruby myapp.rb ``` ### Eigenes Repository Um auf dem neuesten Stand von Sinatras Code zu sein, kann eine lokale Kopie angelegt werden. Gestartet wird in der Anwendung mit dem `sinatra/lib`-Ordner im `LOAD_PATH`: ```shell cd myapp git clone git://github.com/sinatra/sinatra.git ruby -Isinatra/lib myapp.rb ``` Alternativ kann der `sinatra/lib`-Ordner zum `LOAD_PATH` in der Anwendung hinzugefügt werden: ```ruby $LOAD_PATH.unshift File.dirname(__FILE__) + '/sinatra/lib' require 'rubygems' require 'sinatra' get '/ueber' do "Ich laufe auf Version " + Sinatra::VERSION end ``` Um Sinatra-Code von Zeit zu Zeit zu aktualisieren: ```shell cd myproject/sinatra git pull ``` ### Gem erstellen Aus der eigenen lokalen Kopie kann nun auch ein globales Gem gebaut werden: ```shell git clone git://github.com/sinatra/sinatra.git cd sinatra rake sinatra.gemspec rake install ``` Falls Gems als Root installiert werden sollen, sollte die letzte Zeile folgendermaßen lauten: ```shell sudo rake install ``` ## Versions-Verfahren Sinatra folgt dem sogenannten [Semantic Versioning](http://semver.org/), d.h. SemVer und SemVerTag. ## Mehr * [Projekt-Website](http://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) * [#sinatra](irc://chat.freenode.net/#sinatra) auf http://freenode.net Es gibt dort auch immer wieder deutschsprachige Entwickler, die gerne weiterhelfen. * [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-1.4.8/README.hu.md0000644000004100000410000004400213044044066015110 0ustar www-datawww-data# 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 /\A\/hello\/([\w]+)\z/ 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 RACK_ENV környezeti változóban) `:production`-ra van állítva: ```ruby configure :production do ... end ``` Csak akkor fusson le, ha a környezet :production vagy :test: ```ruby configure :production, :test do ... end ``` ## Hibakezelés A hibakezelők ugyanabban a kontextusban futnak le, mint az útvonalak és előszűrők, ezért számukra is elérhetőek mindazok a könyvtárak, amelyek az utóbbiak rendelkezésére is állnak; így például a `haml`, az `erb`, a `halt` stb. ### Nem található Amikor a `Sinatra::NotFound` kivétel fellép, vagy a válasz HTTP státuszkódja 404-es, mindig a `not_found` metódus hívódik meg. ```ruby not_found do 'Sehol sem találom, amit keresel' end ``` ### Hiba Az `error` metódus hívódik meg olyankor, amikor egy útvonal, blokk vagy előszűrő kivételt vált ki. A kivétel objektum lehívható a `sinatra.error` Rack változótól: ```ruby error do 'Elnézést, de valami szörnyű hiba lépett fel - ' + env['sinatra.error'].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_rack_env get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' assert_equal "Songbird-öt használsz!", last_response.body end end ``` Megjegyzés: A beépített Sinatra::Test és Sinatra::TestHarness osztályok a 0.9.2-es kiadástól kezdve elavultnak számítanak. ## Sinatra::Base - Middleware-ek, könyvtárak és moduláris alkalmazások Az alkalmazást felső szinten építeni megfelelhet mondjuk egy kisebb app esetén, ám kifejezetten károsnak bizonyulhat olyan komolyabb, újra felhasználható komponensek készítésekor, mint például egy Rack middleware, Rails metal, egyszerűbb kiszolgáló komponenssel bíró könyvtárak vagy éppen Sinatra kiterjesztések. A felső szintű DSL bepiszkítja az Objektum névteret, ráadásul kisalkalmazásokra szabott beállításokat feltételez (így például egyetlen alkalmazásfájl, `./public` és `./views` könyvtár meglétét, naplózást, kivételkezelő oldalt stb.). Itt jön a képbe a Sinatra::Base osztály: ```ruby require 'sinatra/base' class MyApp < Sinatra::Base set :sessions, true set :foo, 'bar' get '/' do 'Helló Világ!' end end ``` A MyApp osztály immár önálló Rack komponensként, mondjuk Rack middleware-ként vagy alkalmazásként, esetleg Rails metal-ként is tud működni. Közvetlenül használhatod (`use`) vagy futtathatod (`run`) az osztályodat egy rackup konfigurációs állományban (`config.ru`), vagy egy szerverkomponenst tartalmazó könyvtár vezérlésekor: ```ruby MyApp.run! :host => 'localhost', :port => 9090 ``` A Sinatra::Base gyermekosztályaiban elérhető metódusok egyúttal a felső szintű DSL-en keresztül is hozzáférhetők. A legtöbb felső szintű alkalmazás átalakítható Sinatra::Base alapú komponensekké két lépésben: * A fájlban nem a `sinatra`, hanem a `sinatra/base` osztályt kell beimportálni, mert egyébként az összes Sinatra DSL metódus a fő névtérbe kerül. * Az alkalmazás útvonalait, hibakezelőit, szűrőit és beállításait a Sinatra::Base osztály gyermekosztályaiban kell megadni. A `Sinatra::Base` osztály igazából egy üres lap: a legtöbb funkció alapból ki van kapcsolva, beleértve a beépített szervert is. A beállításokkal és az egyes kapcsolók hatásával az [Options and Configuration](http://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) ## 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-1.4.8/CHANGELOG.md0000644000004100000410000014764413044044066015047 0ustar www-datawww-data= 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 (Eliza Sorensen) * Improve documentation. #941, #1069, #1075, #1025, #1052 (Many great folks) * Introduce `Sinatra::Ext` to workaround Rack 1.6 bug to fix Ruby 1.8.7 support. #1080 (Zachary Scott) * Add CONTRIBUTING guide. #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 happend in-between two of those instances, or running a request twice against an application (described in the README). The caching was reverted. See GH#239 and GH#256 for more infos. (Konstantin Haase) * Fixes issues where the top level DSL was interfering with method_missing proxies. This issue surfaced when Rails 3 was used with older Sass versions and Sinatra >= 1.2.0. (Konstantin Haase) * Sinatra::Delegator.delegate is now able to delegate any method names, even those containing special characters. This allows better integration into other programming languages on Rubinius (probably on the JVM, too), like Fancy. (Konstantin Haase) * Remove HEAD request logic and let Rack::Head handle it instead. (Paolo "Nusco" Perrotta) = 1.2.3 / 2011-04-13 * This release is compatible with Tilt 1.3, it will still work with Tilt 1.2.2, however, if you want to use a newer Tilt version, you have to upgrade to at least this version of Sinatra. (Konstantin Haase) * Helpers dealing with time, like `expires`, handle objects that pretend to be numbers, like `ActiveSupport::Duration`, better. (Konstantin Haase) = 1.2.2 / 2011-04-08 * The `:provides => :js` condition now matches both `application/javascript` and `text/javascript`. The `:provides => :xml` condition now matches both `application/xml` and `text/xml`. The `Content-Type` header is set accordingly. If the client accepts both, the `application/*` version is preferred, since the `text/*` versions are deprecated. (Konstantin Haase) * The `provides` condition now handles wildcards in `Accept` headers correctly. Thus `:provides => :html` matches `text/html`, `text/*` and `*/*`. (Konstantin Haase) * When parsing `Accept` headers, `Content-Type` preferences are honored according to RFC 2616 section 14.1. (Konstantin Haase) * URIs passed to the `url` helper or `redirect` may now use any schema to be identified as absolute URIs, not only `http` or `https`. (Konstantin Haase) * Handles `Content-Type` strings that already contain parameters correctly in `content_type` (example: `content_type "text/plain; charset=utf-16"`). (Konstantin Haase) * If a route with an empty pattern is defined (`get("") { ... }`) requests with an empty path info match this route instead of "/". (Konstantin Haase) * In development environment, when running under a nested path, the image URIs on the error pages are set properly. (Konstantin Haase) = 1.2.1 / 2011-03-17 * Use a generated session secret when using `enable :sessions`. (Konstantin Haase) * Fixed a bug where the wrong content type was used if no content type was set and a template engine was used with a different engine for the layout with different default content types, say Less embedded in Slim. (Konstantin Haase) * README translations improved (Gabriel Andretta, burningTyger, Sylvain Desvé, Gregor Schmidt) = 1.2.0 / 2011-03-03 * Added `slim` rendering method for rendering Slim templates. (Steve Hodgkiss) * The `markaby` rendering method now allows passing a block, making inline usage possible. Requires Tilt 1.2 or newer. (Konstantin Haase) * All render methods now take a `:layout_engine` option, allowing to use a layout in a different template language. Even more useful than using this directly (`erb :index, :layout_engine => :haml`) is setting this globally for a template engine that otherwise does not support layouts, like Markdown or Textile (`set :markdown, :layout_engine => :erb`). (Konstantin Haase) * Before and after filters now support conditions, both with and without patterns (`before '/api/*', :agent => /Songbird/`). (Konstantin Haase) * Added a `url` helper method which constructs absolute URLs. Copes with reverse proxies and Rack handlers correctly. Aliased to `to`, so you can write `redirect to('/foo')`. (Konstantin Haase) * If running on 1.9, patterns for routes and filters now support named captures: `get(%r{/hi/(?[^/?#]+)}) { "Hi #{params['name']}" }`. (Steve Price) * All rendering methods now take a `:scope` option, which renders them in another context. Note that helpers and instance variables will be unavailable if you use this feature. (Paul Walker) * The behavior of `redirect` can now be configured with `absolute_redirects` and `prefixed_redirects`. (Konstantin Haase) * `send_file` now allows overriding the Last-Modified header, which defaults to the file's mtime, by passing a `:last_modified` option. (Konstantin Haase) * You can use your own template lookup method by defining `find_template`. This allows, among other things, using more than one views folder. (Konstantin Haase) * Largely improved documentation. (burningTyger, Vasily Polovnyov, Gabriel Andretta, Konstantin Haase) * Improved error handling. (cactus, Konstantin Haase) * Skip missing template engines in tests correctly. (cactus) * Sinatra now ships with a Gemfile for development dependencies, since it eases supporting different platforms, like JRuby. (Konstantin Haase) = 1.1.4 (backports release) / 2011-04-13 * Compatible with Tilt 1.3. (Konstantin Haase) = 1.1.3 / 2011-02-20 * Fixed issues with `user_agent` condition if the user agent header is missing. (Konstantin Haase) * Fix some routing tests that have been skipped by accident (Ross A. Baker) * Fix rendering issues with Builder and Nokogiri (Konstantin Haase) * Replace last_modified helper with better implementation. (cactus, Konstantin Haase) * Fix issue with charset not being set when using `provides` condition. (Konstantin Haase) * Fix issue with `render` not picking up all alternative file extensions for a rendering engine - it was not possible to register ".html.erb" without tricks. (Konstantin Haase) = 1.1.2 / 2010-10-25 Like 1.1.1, but with proper CHANGES file. = 1.1.1 / 2010-10-25 * README has been translated to Russian (Nickolay Schwarz, Vasily Polovnyov) and Portuguese (Luciano Sousa). * Nested templates without a `:layout` option can now be used from the layout template without causing an infinite loop. (Konstantin Haase) * Inline templates are now encoding aware and can therefore be used with unicode characters on Ruby 1.9. Magic comments at the beginning of the file will be honored. (Konstantin Haase) * Default `app_file` is set correctly when running with bundler. Using bundler caused Sinatra not to find the `app_file` and therefore not to find the `views` folder on it's own. (Konstantin Haase) * Better handling of Content-Type when using `send_file`: If file extension is unknown, fall back to `application/octet-stream` and do not override content type if it has already been set, except if `:type` is passed explicitly (Konstantin Haase) * Path is no longer cached if changed between handlers that do pattern matching. This means you can change `request.path_info` in a pattern matching before filter. (Konstantin Haase) * Headers set by cache_control now always set max_age as an Integer, making sure it is compatible with RFC2616. (Konstantin Haase) * Further improved handling of string encodings on Ruby 1.9, templates now honor default_encoding and URLs support unicode characters. (Konstantin Haase) = 1.1.0 / 2010-10-24 * Before and after filters now support pattern matching, including the ability to use captures: "before('/user/:name') { |name| ... }". This avoids manual path checking. No performance loss if patterns are avoided. (Konstantin Haase) * It is now possible to render SCSS files with the `scss` method, which behaves exactly like `sass` except for the different file extension and assuming the SCSS syntax. (Pedro Menezes, Konstantin Haase) * Added `liquid`, `markdown`, `nokogiri`, `textile`, `rdoc`, `radius`, `markaby`, and `coffee` rendering methods for rendering Liquid, Markdown, Nokogiri, Textile, RDoc, Radius, Markaby and CoffeeScript templates. (Konstantin Haase) * Now supports byte-range requests (the HTTP_RANGE header) for static files. Multi-range requests are not supported, however. (Jens Alfke) * You can now use #settings method from class and top level for convenience. (Konstantin Haase) * Setting multiple values now no longer relies on #to_hash and therefore accepts any Enumerable as parameter. (Simon Rozet) * Nested templates default the `layout` option to `false` rather than `true`. This eases the use of partials. If you wanted to render one haml template embedded in another, you had to call `haml :partial, {}, :layout => false`. As you almost never want the partial to be wrapped in the standard layout in this situation, you now only have to call `haml :partial`. Passing in `layout` explicitly is still possible. (Konstantin Haase) * If a the return value of one of the render functions is used as a response body and the content type has not been set explicitly, Sinatra chooses a content type corresponding to the rendering engine rather than just using "text/html". (Konstantin Haase) * README is now available in Chinese (Wu Jiang), French (Mickael Riga), German (Bernhard Essl, Konstantin Haase, burningTyger), Hungarian (Janos Hardi) and Spanish (Gabriel Andretta). The extremely outdated Japanese README has been updated (Kouhei Yanagita). * It is now possible to access Sinatra's template_cache from the outside. (Nick Sutterer) * The `last_modified` method now also accepts DateTime instances and makes sure the header will always be set to a string. (Konstantin Haase) * 599 now is a legal status code. (Steve Shreeve) * This release is compatible with Ruby 1.9.2. Sinatra was trying to read non existent files Ruby added to the call stack. (Shota Fukumori, Konstantin Haase) * Prevents a memory leak on 1.8.6 in production mode. Note, however, that this is due to a bug in 1.8.6 and request will have the additional overhead of parsing templates again on that version. It is recommended to use at least Ruby 1.8.7. (Konstantin Haase) * Compares last modified date correctly. `last_modified` was halting only when the 'If-Modified-Since' header date was equal to the time specified. Now, it halts when is equal or later than the time specified (Gabriel Andretta). * Sinatra is now usable in combination with Rails 3. When mounting a Sinatra application under a subpath in Rails 3, the PATH_INFO is not prefixed with a slash and no routes did match. (José Valim) * Better handling of encodings in 1.9, defaults params encoding to UTF-8. (Konstantin Haase) * `show_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-1.4.8/README.md0000644000004100000410000022003613044044066014500 0ustar www-datawww-data# Sinatra 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) 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) * [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) * [Roll Your Own](#roll-your-own) * [Install Globally](#install-globally) * [Versioning](#versioning) * [Further Reading](#further-reading) ## Routes In Sinatra, a route is an HTTP method paired with a URL-matching pattern. Each route is associated with a block: ```ruby get '/' do .. show something .. end post '/' do .. create something .. end put '/' do .. replace something .. end patch '/' do .. modify something .. end delete '/' do .. annihilate something .. end options '/' do .. appease something .. end link '/' do .. affiliate something .. end unlink '/' do .. separate something .. end ``` Routes are matched in the order they are defined. The first route that matches the request is invoked. Route patterns may include named parameters, accessible via the `params` hash: ```ruby get '/hello/:name' do # matches "GET /hello/foo" and "GET /hello/bar" # params['name'] is 'foo' or 'bar' "Hello #{params['name']}!" end ``` You can also access named parameters via block parameters: ```ruby get '/hello/:name' do |n| # matches "GET /hello/foo" and "GET /hello/bar" # params['name'] is 'foo' or 'bar' # n stores params['name'] "Hello #{n}!" end ``` Route patterns may also include splat (or wildcard) parameters, accessible via the `params['splat']` array: ```ruby get '/say/*/to/*' do # matches /say/hello/to/world params['splat'] # => ["hello", "world"] end get '/download/*.*' do # matches /download/path/to/file.xml params['splat'] # => ["path/to/file", "xml"] end ``` Or with block parameters: ```ruby get '/download/*.*' do |path, ext| [path, ext] # => ["path/to/file", "xml"] end ``` Route matching with Regular Expressions: ```ruby get /\A\/hello\/([\w]+)\z/ 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), the request path might be modified before matching against your routes. ## Conditions Routes may include a variety of matching conditions, such as the user agent: ```ruby get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do "You're using Songbird version #{params['agent'][0]}" end get '/foo' do # Matches non-songbird browsers end ``` Other available conditions are `host_name` and `provides`: ```ruby get '/', :host_name => /^admin\./ do "Admin Area, Access denied!" end get '/', :provides => 'html' do haml :index end get '/', :provides => ['rss', 'atom', 'xml'] do builder :feed end ``` `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 (Fixnum), headers (Hash), response body (responds to #each)]` * An Array with two elements: `[status (Fixnum), response body (responds to #each)]` * An object that responds to `#each` and passes nothing but strings to the given block * A Fixnum representing the status code That way we can, for instance, easily implement a streaming example: ```ruby class Stream def each 100.times { |i| yield "#{i}\n" } end end get('/') { Stream.new } ``` You can also use the `stream` helper method (described below) to reduce boiler plate and embed the streaming logic in the route. ## Custom Route Matchers As shown above, Sinatra ships with built-in support for using String patterns and regular expressions as route matches. However, it does not stop there. You can easily define your own matchers: ```ruby class AllButPattern Match = Struct.new(:captures) def initialize(except) @except = except @captures = Match.new([]) end def match(str) @captures unless @except === str end end def all_but(pattern) AllButPattern.new(pattern) end get all_but("/index") do # ... end ``` Note that the above example might be over-engineered, as it can also be expressed as: ```ruby get // do pass if request.path_info == "/index" # ... end ``` Or, using negative look ahead: ```ruby get %r{^(?!/index$)} do # ... end ``` ## Static Files Static files are served from the `./public` directory. You can specify a different location by setting the `:public_folder` option: ```ruby set :public_folder, File.dirname(__FILE__) + '/static' ``` Note that the public directory name is not included in the URL. A file `./public/css/style.css` is made available as `http://example.com/css/style.css`. Use the `:static_cache_control` setting (see below) to add `Cache-Control` header info. ## Views / Templates Each template language is exposed via its own rendering method. These methods simply return a string: ```ruby get '/' do erb :index end ``` This renders `views/index.erb`. Instead of a template name, you can also just pass in the template content directly: ```ruby get '/' do code = "<%= Time.now %>" erb code end ``` Templates take a second argument, the options hash: ```ruby get '/' do erb :index, :layout => :post end ``` This will render `views/index.erb` embedded in the `views/post.erb` (default is `views/layout.erb`, if it exists). Any options not understood by Sinatra will be passed on to the template engine: ```ruby get '/' do haml :index, :format => :html5 end ``` You can also set options per template language in general: ```ruby set :haml, :format => :html5 get '/' do haml :index end ``` Options passed to the render method override options set via `set`. Available Options:
locals
List of locals passed to the document. Handy with partials. Example: erb "<%= foo %>", :locals => {:foo => "bar"}
default_encoding
String encoding to use if uncertain. Defaults to settings.default_encoding.
views
Views folder to load templates from. Defaults to settings.views.
layout
Whether to use a layout (true or false). If it's a Symbol, specifies what template to use. Example: erb :index, :layout => !request.xhr?
content_type
Content-Type the template produces. Default depends on template language.
scope
Scope to render template under. Defaults to the application instance. If you change this, instance variables and helper methods will not be available.
layout_engine
Template engine to use for rendering the layout. Useful for languages that do not support layouts otherwise. Defaults to the engine used for the template. Example: set :rdoc, :layout_engine => :erb
layout_options
Special options only used for rendering the layout. Example: set :rdoc, :layout_options => { :views => 'views/layouts' }
Templates are assumed to be located directly under the `./views` directory. To use a different views directory: ```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. ### Available Template Languages Some languages have multiple implementations. To specify what implementation to use (and to be thread-safe), you should simply require it first: ```ruby require 'rdiscount' # or require 'bluecloth' get('/') { markdown :index } ``` #### Haml Templates
Dependency haml
File Extension .haml
Example haml :index, :format => :html5
#### Erb Templates
Dependency erubis or erb (included in Ruby)
File Extensions .erb, .rhtml or .erubis (Erubis only)
Example erb :index
#### Builder Templates
Dependency builder
File Extension .builder
Example builder { |xml| xml.em "hi" }
It also takes a block for inline templates (see example). #### Nokogiri Templates
Dependency nokogiri
File Extension .nokogiri
Example nokogiri { |xml| xml.em "hi" }
It also takes a block for inline templates (see example). #### Sass Templates
Dependency sass
File Extension .sass
Example sass :stylesheet, :style => :expanded
#### SCSS Templates
Dependency sass
File Extension .scss
Example scss :stylesheet, :style => :expanded
#### Less Templates
Dependency less
File Extension .less
Example less :stylesheet
#### Liquid Templates
Dependency liquid
File Extension .liquid
Example liquid :index, :locals => { :key => 'value' }
Since you cannot call Ruby methods (except for `yield`) from a Liquid template, you almost always want to pass locals to it. #### Markdown Templates
Dependency Anyone of: RDiscount, RedCarpet, BlueCloth, kramdown, maruku
File Extensions .markdown, .mkd and .md
Example markdown :index, :layout_engine => :erb
It is not possible to call methods from markdown, nor to pass locals to it. You therefore will usually use it in combination with another rendering engine: ```ruby erb :overview, :locals => { :text => markdown(:introduction) } ``` Note that you may also call the `markdown` method from within other templates: ```ruby %h1 Hello From Haml! %p= markdown(:greetings) ``` Since you cannot call Ruby from Markdown, you cannot use layouts written in Markdown. However, it is possible to use another rendering engine for the template than for the layout by passing the `:layout_engine` option. #### Textile Templates
Dependency RedCloth
File Extension .textile
Example textile :index, :layout_engine => :erb
It is not possible to call methods from textile, nor to pass locals to it. You therefore will usually use it in combination with another rendering engine: ```ruby erb :overview, :locals => { :text => textile(:introduction) } ``` Note that you may also call the `textile` method from within other templates: ```ruby %h1 Hello From Haml! %p= textile(:greetings) ``` Since you cannot call Ruby from Textile, you cannot use layouts written in Textile. However, it is possible to use another rendering engine for the template than for the layout by passing the `:layout_engine` option. #### RDoc Templates
Dependency RDoc
File Extension .rdoc
Example rdoc :README, :layout_engine => :erb
It is not possible to call methods from rdoc, nor to pass locals to it. You therefore will usually use it in combination with another rendering engine: ```ruby erb :overview, :locals => { :text => rdoc(:introduction) } ``` Note that you may also call the `rdoc` method from within other templates: ```ruby %h1 Hello From Haml! %p= rdoc(:greetings) ``` Since you cannot call Ruby from RDoc, you cannot use layouts written in RDoc. However, it is possible to use another rendering engine for the template than for the layout by passing the `:layout_engine` option. #### 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). #### 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`. See https://github.com/rtomayko/tilt to learn more about Tilt. ### 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 ``` Note that `enable :sessions` actually stores all data in a cookie. This might not always be what you want (storing lots of data will increase your traffic, for instance). You can use any Rack session middleware: in order to do so, do **not** call `enable :sessions`, but instead pull in your middleware of choice as you would any other middleware: ```ruby use Rack::Session::Pool, :expire_after => 2592000 get '/' do "value = " << session[:value].inspect end get '/:value' do session['value'] = params['value'] end ``` To improve security, the session data in the cookie is signed with a session secret. A random secret is generated for you by Sinatra. However, since this secret will change with every start of your application, you might want to set the secret yourself, so all your application instances share it: ```ruby set :session_secret, 'super secret' ``` If you want to configure it further, you may also store a hash with options in the `sessions` setting: ```ruby set :sessions, :domain => 'foo.com' ``` 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' ``` ### Halting To immediately stop a request within a filter or route use: ```ruby halt ``` You can also specify the status when halting: ```ruby halt 410 ``` Or the body: ```ruby halt 'this will be the body' ``` Or both: ```ruby halt 401, 'go away!' ``` With headers: ```ruby halt 402, {'Content-Type' => 'text/plain'}, 'revenge' ``` It is of course possible to combine a template with `halt`: ```ruby halt erb(:error) ``` ### Passing A route can punt processing to the next matching route using `pass`: ```ruby get '/guess/:who' do pass unless params['who'] == 'Frank' 'You got me!' end get '/guess/*' do 'You missed!' end ``` The route block is immediately exited and control continues with the next matching route. If no matching route is found, a 404 is returned. ### Triggering Another Route Sometimes `pass` is not what you want, instead you would like to get the result of calling another route. Simply use `call` to achieve this: ```ruby get '/foo' do status, headers, body = call env.merge("PATH_INFO" => '/bar') [status, headers, body.map(&:upcase)] end get '/bar' do "bar" end ``` Note that in the example above, you would ease testing and increase performance by simply moving `"bar"` into a helper used by both `/foo` and `/bar`. If you want the request to be sent to the same application instance rather than a duplicate, use `call!` instead of `call`. Check out the Rack specification if you want to learn more about `call`. ### Setting Body, Status Code and Headers It is possible and recommended to set the status code and response body with the return value of the route block. However, in some scenarios you might want to set the body at an arbitrary point in the execution flow. You can do so with the `body` helper method. If you do so, you can use that method from there on to access the body: ```ruby get '/foo' do body "bar" end after do puts body end ``` It is also possible to pass a block to `body`, which will be executed by the Rack handler (this can be used to implement streaming, see "Return Values"). Similar to the body, you can also set the status code and headers: ```ruby get '/foo' do status 418 headers \ "Allow" => "BREW, POST, GET, PROPFIND, WHEN", "Refresh" => "Refresh: 20; http://www.ietf.org/rfc/rfc2324.txt" body "I'm a tea pot!" end ``` Like `body`, `headers` and `status` with no arguments can be used to access their current values. ### Streaming Responses Sometimes you want to start sending out data while still generating parts of the response body. In extreme examples, you want to keep sending data until the client closes the connection. You can use the `stream` helper to avoid creating your own wrapper: ```ruby get '/' do stream do |out| out << "It's gonna be legen -\n" sleep 0.5 out << " (wait for it) \n" sleep 1 out << "- dary!\n" end end ``` This allows you to implement streaming APIs, [Server Sent Events](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 ``` ### 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 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): ```ruby require "rack/cache" require "sinatra" use Rack::Cache get '/' do cache_control :public, :max_age => 36000 sleep 5 "hello" end ``` Use the `:static_cache_control` setting (see below) to add `Cache-Control` header info to static files. According to RFC 2616, your application should behave differently if the If-Match or If-None-Match header is set to `*`, depending on whether the resource requested is already in existence. Sinatra assumes resources for safe (like get) and idempotent (like put) requests are already in existence, whereas other resources (for instance 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, 2012') "still time" end ``` This method is used internally by `expires`, `last_modified` and akin. You can therefore easily extend the behavior of those methods by overriding `time_for` in your application: ```ruby helpers do def time_for(value) case value when :yesterday then Time.now - 24*60*60 when :tomorrow then Time.now + 24*60*60 else super end end end get '/' do last_modified :yesterday expires :tomorrow "hello" end ``` ### Looking Up Template Files The `find_template` helper is used to find template files for rendering: ```ruby find_template settings.views, 'foo', Tilt[:haml] do |file| puts "could be #{file}" end ``` This is not really useful. But it is useful that you can actually override this method to hook in your own lookup mechanism. For instance, if you want to be able to use more than one view directory: ```ruby set :views, ['views', 'templates'] helpers do def find_template(views, name, engine, &block) Array(views).each { |v| super(v, name, engine, &block) } end end ``` Another example would be using different directories for different engines: ```ruby set :views, :sass => 'views/sass', :haml => 'templates', :default => 'views' helpers do def find_template(views, name, engine, &block) _, folder = views.detect { |k,v| engine == Tilt[k] } folder ||= views[:default] super(folder, name, engine, &block) end end ``` You can also easily wrap this up in an extension and share with others! Note that `find_template` does not check if the file really exists but rather calls the given block for all possible paths. This is not a performance issue, since `render` will use `break` as soon as a file is found. Also, template locations (and content) will be cached if you are not running in development mode. You should keep that in mind if you write a really crazy method. ## Configuration Run once, at startup, in any environment: ```ruby configure do # setting one option set :option, 'value' # setting multiple options set :a => 1, :b => 2 # same as `set :option, true` enable :option # same as `set :option, false` disable :option # you can also have dynamic settings with blocks set(:css_dir) { File.join(views, 'css') } end ``` Run only when the environment (`RACK_ENV` environment variable) is set to `:production`: ```ruby configure :production do ... end ``` Run when the environment is set to either `:production` or `:test`: ```ruby configure :production, :test do ... end ``` You can access those options via `settings`: ```ruby configure do set :foo, 'bar' end get '/' do settings.foo? # => true settings.foo # => 'bar' ... end ``` ### Configuring attack protection Sinatra is using [Rack::Protection](https://github.com/sinatra/rack-protection#readme) to defend your application against common, opportunistic attacks. You can easily disable this behavior (which will open up your application to tons of common vulnerabilities): ```ruby disable :protection ``` To skip a single defense layer, set `protection` to an options hash: ```ruby set :protection, :except => :path_traversal ``` You can also hand in an array in order to disable a list of protections: ```ruby set :protection, :except => [:path_traversal, :session_hijacking] ``` By default, Sinatra will only set up session based protection if `:sessions` has been enabled. Sometimes you want to set up sessions on your own, though. In that case you can get it to set up session based protections by passing the `:session` option: ```ruby use Rack::Session::Pool set :protection, :session => true ``` ### Available Settings
absolute_redirects
If disabled, Sinatra will allow relative redirects, however, Sinatra will no longer conform with RFC 2616 (HTTP 1.1), which only allows absolute redirects.
Enable if your app is running behind a reverse proxy that has not been set up properly. Note that the url helper will still produce absolute URLs, unless you pass in false as the second parameter.
Disabled by default.
add_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['RACK_ENV'], or "development" if not available.
logging
Use the logger.
lock
Places a lock around every request, only running processing on request per Ruby process concurrently.
Enabled if your app is not thread-safe. Disabled by default.
method_override
Use _method magic to allow put/delete forms in browsers that don't support it.
port
Port to listen on. Only used for built-in server.
prefixed_redirects
Whether or not to insert request.script_name into redirects if no absolute path is given. That way redirect '/foo' would behave like redirect to('/foo'). Disabled 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.
reload_templates
Whether or not to reload templates between requests. Enabled in development mode.
root
Path to project root folder. Inferred from app_file setting if not set.
raise_errors
Raise exceptions (will stop application). Enabled by default when environment is set to "test", disabled otherwise.
run
If enabled, Sinatra will handle starting the web server. Do not enable if using rackup or other means.
running
Is the built-in server running now? Do not change this setting!
server
Server or list of servers to use for built-in server. Order indicates priority, default depends on Ruby implementation.
sessions
Enable cookie-based sessions support using Rack::Session::Cookie. See 'Using Sessions' section for more information.
show_exceptions
Show a stack trace in the browser when an exception happens. Enabled by default when environment is set to "development", disabled otherwise.
Can also be set to :after_handler to trigger app-specified error handling before showing a stack trace in the browser.
static
Whether Sinatra should handle serving static files.
Disable when using a server able to do this on its own.
Disabling will boost performance.
Enabled 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 `RACK_ENV` environment variable. The default value is `"development"`. In the `"development"` environment all templates are reloaded between requests, and special `not_found` and `error` handlers display stack traces in your browser. In the `"production"` and `"test"` environments, templates are cached by default. To run different environments, set the `RACK_ENV` environment variable: ```shell RACK_ENV=production ruby my_app.rb ``` You can use predefined methods: `development?`, `test?` and `production?` to check the current environment setting: ```ruby get '/' do if settings.development? "development!" else "not development!" end end ``` ## Error Handling Error handlers run within the same context as routes and before filters, which means you get all the goodies it has to offer, like `haml`, `erb`, `halt`, etc. ### Not Found When a `Sinatra::NotFound` exception is raised, or the response's status code is 404, the `not_found` handler is invoked: ```ruby not_found do 'This is nowhere to be found.' end ``` ### Error The `error` handler is invoked any time an exception is raised from a route block or a filter. 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](http://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#readm), 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_rack_env get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' assert_equal "You're using Songbird!", last_response.body end end ``` Note: If you are using Sinatra in the modular style, replace `Sinatra::Application` above with the class name of your app. ## Sinatra::Base - Middleware, Libraries, and Modular Apps Defining your app at the top-level works well for micro-apps but has considerable drawbacks when building reusable components such as Rack middleware, Rails metal, simple libraries with a server component, or even Sinatra extensions. The top-level assumes a micro-app style configuration (e.g., a single application file, `./public` and `./views` directories, logging, exception detail page, etc.). That's where `Sinatra::Base` comes into play: ```ruby require 'sinatra/base' class MyApp < Sinatra::Base set :sessions, true set :foo, 'bar' get '/' do 'Hello world!' end end ``` The methods available to `Sinatra::Base` subclasses are exactly the same as those available via the top-level DSL. Most top-level apps can be converted to `Sinatra::Base` components with two modifications: * Your file should require `sinatra/base` instead of `sinatra`; otherwise, all of Sinatra's DSL methods are imported into the main namespace. * Put your app's routes, error handlers, filters, and options in a subclass of `Sinatra::Base`. `Sinatra::Base` is a blank slate. Most options are disabled by default, including the built-in server. See [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/Ramaze/Camping/...): ```ruby require 'sinatra/base' class LoginScreen < Sinatra::Base enable :sessions get('/login') { haml :login } post('/login') do if params['name'] == 'admin' && params['password'] == 'admin' session['user_name'] = params['name'] else redirect '/login' end end end class MyApp < Sinatra::Base # middleware will run before filters use LoginScreen before do unless session['user_name'] halt "Access denied, please login." end end get('/') { "Hello #{session['user_name']}." } end ``` ### Dynamic Application Creation Sometimes you want to create new applications at runtime without having to assign them to a constant. You can do this with `Sinatra.new`: ```ruby require 'sinatra/base' my_app = Sinatra.new { get('/') { "hi" } } my_app.run! ``` It takes the application to inherit from as an optional argument: ```ruby # config.ru (run with rackup) require 'sinatra/base' controller = Sinatra.new do enable :logging helpers MyHelpers end map('/a') do run Sinatra.new(controller) { get('/') { 'a' } } end map('/b') do run Sinatra.new(controller) { get('/') { 'b' } } end ``` This is especially useful for testing Sinatra extensions or using Sinatra in your own library. This also makes using Sinatra as middleware extremely easy: ```ruby require 'sinatra/base' use Sinatra do get('/') { ... } end run RailsProject::Application ``` ## Scopes and Binding The scope you are currently in determines what methods and variables are available. ### Application/Class Scope Every Sinatra application corresponds to a subclass of `Sinatra::Base`. If you are using the top-level DSL (`require 'sinatra'`), then this class is `Sinatra::Application`, otherwise it is the subclass you created explicitly. At class level you have methods like `get` or `before`, but you cannot access the `request` or `session` objects, as there is only a single application class for all requests. Options created via `set` are methods at class level: ```ruby class MyApp < Sinatra::Base # Hey, I'm in the application scope! set :foo, 42 foo # => 42 get '/foo' do # Hey, I'm no longer in the application scope! end end ``` You have the application scope binding inside: * Your application class body * Methods defined by extensions * The block passed to `helpers` * Procs/blocks used as value for `set` * The block passed to `Sinatra.new` You can reach the scope object (the class) like this: * Via the object passed to configure blocks (`configure { |c| ... }`) * `settings` from within the request scope ### Request/Instance Scope For every incoming request, a new instance of your application class is created, and all handler blocks run in that scope. From within this scope you can access the `request` and `session` objects or call rendering methods like `erb` or `haml`. You can access the application scope from within the request scope via the `settings` helper: ```ruby class MyApp < Sinatra::Base # Hey, I'm in the application scope! get '/define_route/:name' do # Request scope for '/define_route/:name' @value = 42 settings.get("/#{params['name']}") do # Request scope for "/#{params['name']}" @value # => nil (not the same request) end "Route defined!" end end ``` You have the request scope binding inside: * get, head, post, put, delete, options, patch, link and unlink blocks * before and after filters * helper methods * templates/views ### Delegation Scope The delegation scope just forwards methods to the class scope. However, it does not behave exactly like the class scope, as you do not have the class binding. Only methods explicitly marked for delegation are available, and you do not share variables/state with the class scope (read: you have a different `self`). You can explicitly add method delegations by calling `Sinatra::Delegator.delegate :method_name`. You have the delegate scope binding inside: * The top level binding, if you did `require "sinatra"` * An object extended with the `Sinatra::Delegator` mixin Have a look at the code for yourself: here's the [Sinatra::Delegator mixin](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/base.rb#L1609-1633) being [extending the main object](https://github.com/sinatra/sinatra/blob/ca06364/lib/sinatra/main.rb#L28-30). ## Command Line Sinatra applications can be run directly: ```shell ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER] ``` Options are: ``` -h # help -p # set the port (default is 4567) -o # set the host (default is 0.0.0.0) -e # set the environment (default is development) -s # specify rack server/handler (default is thin) -x # turn on the mutex lock (default is off) ``` ### Multi-threading _Paraphrasing from [this StackOverflow answer][so-answer] 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 ``` [so-answer]: http://stackoverflow.com/questions/6278817/is-sinatra-multi-threaded/6282999#6282999) ## Requirement The following Ruby versions are officially supported:
Ruby 1.8.7
1.8.7 is fully supported, however, if nothing is keeping you from it, we recommend upgrading or switching to JRuby or Rubinius. Support for 1.8.7 will not be dropped before Sinatra 2.0. Ruby 1.8.6 is no longer supported.
Ruby 1.9.2
1.9.2 is fully supported. Do not use 1.9.2p0, as it is known to cause segmentation faults when running Sinatra. Official support will continue at least until the release of Sinatra 1.5.
Ruby 1.9.3
1.9.3 is fully supported and recommended. Please note that switching to 1.9.3 from an earlier version will invalidate all sessions. 1.9.3 will be supported until the release of Sinatra 2.0.
Ruby 2.x
2.x 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.
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 1.8.7. ## The Bleeding Edge If you would like to use Sinatra's latest bleeding-edge code, feel free to run your application against the master branch, it should be rather stable. We also push out prerelease gems from time to time, so you can do a ```shell gem install sinatra --pre ``` to get some of the latest features. ### With Bundler If you want to run your application with the latest Sinatra, using [Bundler](http://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 gem 'activerecord', '~> 3.0' # maybe you also need ActiveRecord 3.x ``` Note that you will have to list all your application's dependencies in the `Gemfile`. Sinatra's direct dependencies (Rack and Tilt) will, however, be automatically fetched and added by Bundler. Now you can run your app like this: ```shell bundle exec ruby myapp.rb ``` ### Roll Your Own Create a local clone and run your app with the `sinatra/lib` directory on the `$LOAD_PATH`: ```shell cd myapp git clone git://github.com/sinatra/sinatra.git ruby -I sinatra/lib myapp.rb ``` To update the Sinatra sources in the future: ```shell cd myapp/sinatra git pull ``` ### Install Globally You can build the gem on your own: ```shell git clone git://github.com/sinatra/sinatra.git cd sinatra rake sinatra.gemspec rake install ``` If you install gems as root, the last step should be: ```shell sudo rake install ``` ## Versioning Sinatra follows [Semantic Versioning](http://semver.org/), both SemVer and SemVerTag. ## Further Reading * [Project Website](http://www.sinatrarb.com/) - Additional documentation, news, and links to other resources. * [Contributing](http://www.sinatrarb.com/contributing) - Find a bug? Need help? Have a patch? * [Issue tracker](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) on http://freenode.net * [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/) 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 http://www.rubydoc.info/ * [CI server](https://travis-ci.org/sinatra/sinatra)