Sinatra
¶ ↑
注) 本文書は英語から翻訳したものであり、その内容が最新でない場合もあります。最新の情報はオリジナルの英語版を参照してください。
Sinatraは最小の労力でRubyによるWebアプリケーションを手早く作るためのDSLです。
# myapp.rb require 'sinatra' get '/' do 'Hello world!' end
gemをインストールし、
gem install sinatra
次のように実行します。
ruby myapp.rb
localhost:4567 を開きます。
ThinがあればSinatraはこれを利用するので、gem install thin
することをお薦めします。
目次¶ ↑
ルーティング(Routes)¶ ↑
Sinatraでは、ルーティングはHTTPメソッドとURLマッチングパターンがペアになっています。 ルーティングはブロックに結び付けられています。
get '/' do .. 何か見せる .. end post '/' do .. 何か生成する .. end put '/' do .. 何か更新する .. end patch '/' do .. 何か修正する .. end delete '/' do .. 何か削除する .. end options '/' do .. 何か満たす .. end link '/' do .. 何かリンクを張る .. end unlink '/' do .. 何かアンリンクする .. end
ルーティングは定義された順番にマッチします。 リクエストに最初にマッチしたルーティングが呼び出されます。
トレイリングスラッシュを付けたルートは、そうでないルートと異なったものになります。
get '/foo' do # Does not match "GET /foo/" end
ルーティングのパターンは名前付きパラメータを含むことができ、 params
ハッシュで取得できます。
get '/hello/:name' do # "GET /hello/foo" と "GET /hello/bar" にマッチ # params['name'] は 'foo' か 'bar' "Hello #{params['name']}!" end
また、ブロックパラメータで名前付きパラメータにアクセスすることもできます。
get '/hello/:name' do |n| # "GET /hello/foo" と "GET /hello/bar" にマッチ # params['name'] は 'foo' か 'bar' # n が params['name'] を保持 "Hello #{n}!" end
ルーティングパターンはアスタリスク(すなわちワイルドカード)を含むこともでき、 params['splat']
で取得できます。
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
ここで、ブロックパラメータを使うこともできます。
get '/download/*.*' do |path, ext| [path, ext] # => ["path/to/file", "xml"] end
ルーティングを正規表現にマッチさせることもできます。
get /\/hello\/([\w]+)/ do "Hello, #{params['captures'].first}!" end
ここでも、ブロックパラメータが使えます。
get %r{/hello/([\w]+)} do |c| "Hello, #{c}!" end
ルーティングパターンは、オプショナルパラメータを取ることもできます。
get '/posts/:format?' do # "GET /posts/" と "GET /posts/json", "GET /posts/xml" の拡張子などにマッチ end
ところで、ディレクトリトラバーサル攻撃防御設定を無効にしないと(下記参照)、 ルーティングにマッチする前にリクエストパスが修正される可能性があります。
条件(Conditions)¶ ↑
ルーティングにはユーザエージェントのようなさまざまな条件を含めることができます。
get '/foo', :agent => /Songbird (\d\.\d)[\d\/]*?/ do "Songbirdのバージョン #{params['agent'][0]}を使ってます。" end get '/foo' do # Songbird以外のブラウザにマッチ end
ほかにhost_name
とprovides
条件が利用可能です。
get '/', :host_name => /^admin\./ do "Adminエリアです。アクセスを拒否します!" end get '/', :provides => 'html' do haml :index end get '/', :provides => ['rss', 'atom', 'xml'] do builder :feed end
独自の条件を定義することも簡単にできます。
set(:probability) { |value| condition { rand <= value } } get '/win_a_car', :probability => 0.1 do "あなたの勝ちです!" end get '/win_a_car' do "残念、あなたの負けです。" end
複数の値を取る条件には、アスタリスクを使います。
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)
これにより、例えばストリーミングを簡単に実装することができます。
class Stream def each 100.times { |i| yield "#{i}\n" } end end get('/') { Stream.new }
後述するstream
ヘルパーメソッドを使って、定型パターンを減らしつつストリーミングロジックをルーティングに埋め込むこともできます。
カスタムルーティングマッチャー(Custom Route Matchers)¶ ↑
先述のようにSinatraはルーティングマッチャーとして、文字列パターンと正規表現を使うことをビルトインでサポートしています。しかしこれに留まらず、独自のマッチャーを簡単に定義することもできるのです。
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
ノート: この例はオーバースペックであり、以下のようにも書くことができます。
get // do pass if request.path_info == "/index" # ... end
または、否定先読みを使って:
get %r{(?!/index)} do # ... end
静的ファイル(Static Files)¶ ↑
静的ファイルは./public
ディレクトリから配信されます。 :public_folder
オプションを指定することで別の場所を指定することができます。
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)¶ ↑
各テンプレート言語はそれ自身のレンダリングメソッドを通して展開されます。それらのメソッドは単に文字列を返します。
get '/' do erb :index end
これは、views/index.erb
をレンダリングします。
テンプレート名を渡す代わりに、直接そのテンプレートの中身を渡すこともできます。
get '/' do code = "<%= Time.now %>" erb code end
テンプレートのレイアウトは第2引数のハッシュ形式のオプションをもとに表示されます。
get '/' do erb :index, :layout => :post end
これは、views/post.erb
内に埋め込まれたviews/index.erb
をレンダリングします(デフォルトはviews/layout.erb
があればそれになります)。
Sinatraが理解できないオプションは、テンプレートエンジンに渡されることになります。
get '/' do haml :index, :format => :html5 end
テンプレート言語ごとにオプションをセットすることもできます。
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
ディレクトリ下に配置されています。 他のディレクトリを使用する場合の例:
set :views, settings.root + '/templates'
テンプレートの参照は、テンプレートがサブディレクトリ内にある場合でも常にシンボルで指定することを覚えておいてください。 (これは:'subdir/template'
または'subdir/template'.to_sym
のように指定することを意味します。) レンダリングメソッドにシンボルではなく文字列を渡してしまうと、そのまま文字列として出力してしまいます。
リテラルテンプレート(Literal Templates)¶ ↑
get '/' do haml '%div.title Hello World' end
これはテンプレート文字列をレンダリングしています。 テンプレート文字列に関連するファイルパスや行数を:path
や:line
オプションで指定することで、バックトレースを明確にすることができます。
get '/' do haml '%div.title Hello World', :path => 'examples/file.haml', :line => 3 end
利用可能なテンプレート言語¶ ↑
いくつかの言語には複数の実装があります。使用する(そしてスレッドセーフにする)実装を指定するには、それを最初にrequireしてください。
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に変数を渡すこともできません。 それゆえ、他のレンダリングエンジンとの組み合わせで使うのが普通です。
erb :overview, :locals => { :text => markdown(:introduction) }
ノート: 他のテンプレート内でmarkdown
メソッドを呼び出せます。
MarkdownからはRubyを呼ぶことができないので、Markdownで書かれたレイアウトを使うことはできません。しかしながら、:layout_engine
オプションを渡すことでテンプレートのものとは異なるレンダリングエンジンをレイアウトのために使うことができます。
Textile テンプレート¶ ↑
依存 | RedCloth |
ファイル拡張子 | .textile |
例 | textile :index, :layout_engine => :erb |
Textileからメソッドを呼び出すことも、localsに変数を渡すこともできません。 それゆえ、他のレンダリングエンジンとの組み合わせで使うのが普通です。
erb :overview, :locals => { :text => textile(:introduction) }
ノート: 他のテンプレート内でtextile
メソッドを呼び出せます。
TexttileからはRubyを呼ぶことができないので、Textileで書かれたレイアウトを使うことはできません。しかしながら、:layout_engine
オプションを渡すことでテンプレートのものとは異なるレンダリングエンジンをレイアウトのために使うことができます。
RDoc テンプレート¶ ↑
依存 | RDoc |
ファイル拡張子 | .rdoc |
例 | rdoc :README, :layout_engine => :erb |
RDocからメソッドを呼び出すことも、localsに変数を渡すこともできません。 それゆえ、他のレンダリングエンジンとの組み合わせで使うのが普通です。
erb :overview, :locals => { :text => rdoc(:introduction) }
ノート: 他のテンプレート内でrdoc
メソッドを呼び出せます。
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に変数を渡すこともできません。 それゆえ、他のレンダリングエンジンとの組み合わせで使うのが普通です。
erb :overview, :locals => { :text => creole(:introduction) }
ノート: 他のテンプレート内でcreole
メソッドを呼び出せます。
CreoleからはRubyを呼ぶことができないので、Creoleで書かれたレイアウトを使うことはできません。しかしながら、:layout_engine
オプションを渡すことでテンプレートのものとは異なるレンダリングエンジンをレイアウトのために使うことができます。
MediaWiki テンプレート¶ ↑
依存 | WikiCloth |
ファイル拡張子 | .mediawiki および .mw |
例 | mediawiki :wiki, :layout_engine => :erb |
MediaWikiのテンプレートは直接メソッドから呼び出したり、ローカル変数を通すことはできません。それゆえに、通常は別のレンダリングエンジンと組み合わせて利用します。
erb :overview, :locals => { :text => mediawiki(:introduction) }
ノート: 他のテンプレートから部分的にmediawiki
メソッドを呼び出すことも可能です。
CoffeeScript テンプレート¶ ↑
依存 | CoffeeScript および JavaScriptの起動方法 |
ファイル拡張子 | .coffee |
例 | coffee :index |
Stylus テンプレート¶ ↑
依存 | Stylus および JavaScriptの起動方法 |
ファイル拡張子 | .styl |
例 | stylus :index |
Stylusテンプレートを使えるようにする前に、まずstylus
とstylus/tilt
を読み込む必要があります。
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
を使って変換されます。
json = { :foo => 'bar' } json[:baz] = key
:callback
および:variable
オプションは、レンダリングされたオブジェクトを装飾するために使うことができます。
var resource = {"foo":"bar","baz":"qux"}; present(resource);
WLang テンプレート¶ ↑
依存 | wlang |
ファイル拡張子 | .wlang |
例 | wlang :index, :locals => { :key => 'value' } |
WLang内でのRubyメソッドの呼び出しは一般的ではないので、ほとんどの場合にlocalsを指定する必要があるでしょう。しかしながら、WLangで書かれたレイアウトはyield
をサポートしています。
テンプレート内での変数へのアクセス¶ ↑
テンプレートはルーティングハンドラと同じコンテキストの中で評価されます。ルーティングハンドラでセットされたインスタンス変数はテンプレート内で直接使うことができます。
get '/:id' do @foo = Foo.find(params['id']) haml '%h1= @foo.name' end
また、ローカル変数のハッシュで明示的に指定することもできます。
get '/:id' do foo = Foo.find(params['id']) haml '%h1= bar.name', :locals => { :bar => foo } end
これは他のテンプレート内で部分テンプレートとして表示する典型的な手法です。
yield
を伴うテンプレートとネストしたレイアウト¶ ↑
レイアウトは通常、yield
を呼ぶ単なるテンプレートに過ぎません。 そのようなテンプレートは、既に説明した:template
オプションを通して使われるか、または次のようなブロックを伴ってレンダリングされます。
erb :post, :layout => false do erb :index end
このコードは、erb :index, :layout => :post
とほぼ等価です。
レンダリングメソッドにブロックを渡すスタイルは、ネストしたレイアウトを作るために最も役立ちます。
erb :main_layout, :layout => false do erb :admin_layout do erb :user end end
これはまた次のより短いコードでも達成できます。
erb :admin_layout, :layout => :main_layout do erb :user end
現在、次のレンダリングメソッドがブロックを取れます: erb
, haml
, liquid
, slim
, wlang
。 また汎用のrender
メソッドもブロックを取れます。
インラインテンプレート(Inline Templates)¶ ↑
テンプレートはソースファイルの最後で定義することもできます。
require 'sinatra' get '/' do haml :index end __END__
ノート: Sinatraをrequireするソースファイル内で定義されたインラインテンプレートは自動的に読み込まれます。他のソースファイル内にインラインテンプレートがある場合にはenable :inline_templates
を明示的に呼んでください。
名前付きテンプレート(Named Templates)¶ ↑
テンプレートはトップレベルのtemplate
メソッドで定義することもできます。
template :layout do "%html\n =yield\n" end template :index do '%div.title Hello World!' end get '/' do haml :index end
「layout」という名前のテンプレートが存在する場合は、そのテンプレートファイルは他のテンプレートがレンダリングされる度に使用されます。:layout => false
で個別に、またはset :haml, :layout => false
でデフォルトとして、レイアウトを無効にすることができます。
get '/' do haml :index, :layout => !request.xhr? end
ファイル拡張子の関連付け¶ ↑
任意のテンプレートエンジンにファイル拡張子を関連付ける場合は、Tilt.register
を使います。例えば、Textileテンプレートにtt
というファイル拡張子を使いたい場合は、以下のようにします。
Tilt.register :tt, Tilt[:textile]
オリジナルテンプレートエンジンの追加¶ ↑
まず、Tiltでそのエンジンを登録し、次にレンダリングメソッドを作ります。
Tilt.register :myat, MyAwesomeTemplateEngine helpers do def myat(*args) render(:myat, *args) end end get '/' do myat :index end
これは、./views/index.myat
をレンダリングします。Tiltについての詳細は、github.com/rtomayko/tilt を参照してください。
カスタムロジックを使用したテンプレートの探索¶ ↑
オリジナルテンプレートの検索メカニズムを実装するためには、#find_template
メソッドを実装します。
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フィルタは、リクエストのルーティングと同じコンテキストで各リクエストの前に評価され、それによってリクエストとレスポンスを変更可能にします。フィルタ内でセットされたインスタンス変数はルーティングとテンプレートからアクセスすることができます。
before do @note = 'Hi!' request.path_info = '/foo/bar/baz' end get '/foo/*' do @note #=> 'Hi!' params['splat'] #=> 'bar/baz' end
afterフィルタは、リクエストのルーティングと同じコンテキストで各リクエストの後に評価され、それによってこれもリクエストとレスポンスを変更可能にします。beforeフィルタとルーティング内でセットされたインスタンス変数はafterフィルタからアクセスすることができます。
after do puts response.status end
ノート: body
メソッドを使わずにルーティングから文字列を返すだけの場合、その内容はafterフィルタでまだ利用できず、その後に生成されることになります。
フィルタにはオプションとしてパターンを渡すことができ、この場合はリクエストのパスがパターンにマッチした場合にのみフィルタが評価されるようになります。
before '/protected/*' do authenticate! end after '/create/:slug' do |slug| session[:last_slug] = slug end
ルーティング同様、フィルタもまた条件を取ることができます。
before :agent => /Songbird/ do # ... end after '/blog/*', :host_name => 'example.com' do # ... end
ヘルパー(Helpers)¶ ↑
トップレベルのhelpers
メソッドを使用してルーティングハンドラやテンプレートで使うヘルパーメソッドを定義できます。
helpers do def bar(name) "#{name}bar" end end get '/:name' do bar(params['name']) end
あるいは、ヘルパーメソッドをモジュール内で個別に定義することもできます。
module FooUtils def foo(name) "#{name}foo" end end module BarUtils def bar(name) "#{name}bar" end end helpers FooUtils, BarUtils
その効果は、アプリケーションクラスにモジュールをインクルードするのと同じです。
セッションの使用¶ ↑
セッションはリクエスト間での状態維持のために使用されます。セッションを有効化すると、ユーザセッションごとに一つのセッションハッシュが与えられます。
enable :sessions get '/' do "value = " << session[:value].inspect end get '/:value' do session[:value] = params['value'] end
ノート: enable :sessions
は実際にはすべてのデータをクッキーに保持します。これは必ずしも期待通りのものにならないかもしれません(例えば、大量のデータを保持することでトラフィックが増大するなど)。Rackセッションミドルウェアの利用が可能であり、その場合はenable :sessions
を呼ばずに、選択したミドルウェアを他のミドルウェアのときと同じようにして取り込んでください。
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によりランダムな秘密鍵が個別に生成されます。しかし、この秘密鍵はアプリケーションの立ち上げごとに変わってしまうので、すべてのアプリケーションのインスタンスで共有できる秘密鍵をセットしたくなるかもしれません。
set :session_secret, 'super secret'
更に、設定変更をしたい場合は、sessions
の設定においてオプションハッシュを保持することもできます。
set :sessions, :domain => 'foo.com'
foo.comのサブドメイン上のアプリ間でセッションを共有化したいときは、代わりにドメインの前に . を付けます。
set :sessions, :domain => '.foo.com'
セッションミドルウェアの選択¶ ↑
enable :sessions
とすることで、クッキー内の全てのデータを実際に保存してしまうことに注意してください。 これは、あなたが望む挙動ではない(例えば、大量のデータを保存することでトラフィックが増大してしまう)かもしれません。 あなたは、次のいずれかの方法によって、任意のRackセッションミドルウェアを使用することができます。
enable :sessions set :session_store, Rack::Session::Pool
オプションのハッシュを設定するためには、次のようにします。
set :sessions, :expire_after => 2592000 set :session_store, Rack::Session::Pool
他の方法はenable :sessions
をしないで、他のミドルウェアの選択と同様にあなた自身でミドルウェアを選択することです。
この方法を選択する場合は、セッションベースの保護はデフォルトで有効にならないということに注意することが重要です。
これを満たすためのRackミドルウェアを追加することが必要になります。
use Rack::Session::Pool, :expire_after => 2592000 use Rack::Protection::RemoteToken use Rack::Protection::SessionHijacking
より詳しい情報は、「攻撃防御に対する設定」の項を参照してください。
停止(Halting)¶ ↑
フィルタまたはルーティング内で直ちにリクエストを止める場合
halt
この際、ステータスを指定することもできます。
halt 410
body部を指定することも、
halt 'ここにbodyを書く'
ステータスとbody部を指定することも、
halt 401, '立ち去れ!'
ヘッダを付けることもできます。
halt 402, {'Content-Type' => 'text/plain'}, 'リベンジ'
もちろん、テンプレートをhalt
に結びつけることも可能です。
halt erb(:error)
パッシング(Passing)¶ ↑
ルーティングはpass
を使って次のルーティングに飛ばすことができます。
get '/guess/:who' do pass unless params['who'] == 'Frank' "見つかっちゃった!" end get '/guess/*' do "はずれです!" end
ルーティングブロックからすぐに抜け出し、次にマッチするルーティングを実行します。マッチするルーティングが見当たらない場合は404が返されます。
別ルーティングの誘発¶ ↑
pass
を使ってルーティングを飛ばすのではなく、他のルーティングを呼んだ結果を得たいという場合があります。 これはcall
を使用することで実現できます。
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
ヘルパーメソッドを使えばそれができます。そうすると、それ以降、ボディにアクセスするためにそのメソッドを使うことができるようになります。
get '/foo' do body "bar" end after do puts body end
また、body
にはブロックを渡すことができ、これはRackハンドラにより実行されることになります(これはストリーミングを実装するのに使われます。“戻り値”の項を参照してください。)
ボディと同様に、ステータスコードおよびヘッダもセットできます。
get '/foo' do status 418 headers \ "Allow" => "BREW, POST, GET, PROPFIND, WHEN", "Refresh" => "Refresh: 20; https://www.ietf.org/rfc/rfc2324.txt" body "I'm a tea pot!" end
引数を伴わないbody
、headers
、status
などは、それらの現在の値にアクセスするために使えます。
ストリーミングレスポンス(Streaming Responses)¶ ↑
レスポンスボディの部分を未だ生成している段階で、データを送り出したいということがあります。極端な例では、クライアントがコネクションを閉じるまでデータを送り続けたいことがあります。stream
ヘルパーを使えば、独自ラッパーを作る必要はありません。
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の土台に使うことができます。また、一部のコンテンツが遅いリソースに依存しているときに、スループットを上げるために使うこともできます。
ノート: ストリーミングの挙動、特に並行リクエスト(cuncurrent requests)の数は、アプリケーションを提供するのに使われるWebサーバに強く依存します。いくつかのサーバは、ストリーミングを全くサポートしません。サーバがストリーミングをサポートしない場合、ボディはstream
に渡されたブロックの実行が終了した後、一度に全部送られることになります。ストリーミングは、Shotgunを使った場合は全く動作しません。
オプション引数がkeep_open
にセットされている場合、ストリームオブジェクト上でclose
は呼ばれず、実行フローの任意の遅れたタイミングでユーザがこれを閉じることを可能にします。これはThinやRainbowsのようなイベント型サーバ上でしか機能しません。他のサーバでは依然ストリームは閉じられます。
# ロングポーリング set :server, :thin connections = [] get '/subscribe' do # サーバイベントにおけるクライアントの関心を登録 stream(:keep_open) do |out| connections << out # 死んでいるコネクションを排除 connections.reject!(&:closed?) end end post '/message' do connections.each do |out| # クライアントへ新規メッセージ到着の通知 out << params['message'] << "\n" # クライアントへの再接続の指示 out.close end # 肯定応答 "message received" end
クライアントはソケットに書き込もうとしている接続を閉じることも可能です。そのため、記述しようとする前にout.closed?
をチェックすることを勧めます。
ロギング(Logging)¶ ↑
リクエストスコープにおいて、logger
ヘルパーはLogger
インスタンスを作り出します。
get '/' do logger.info "loading data" # ... end
このロガーは、自動でRackハンドラのロギング設定を参照します。ロギングが無効(disabled)にされている場合、このメソッドはダミーオブジェクトを返すので、ルーティングやフィルタにおいて特に心配することはありません。
ノート: ロギングは、Sinatra::Application
に対してのみデフォルトで有効にされているので、Sinatra::Base
を継承している場合は、ユーザがこれを有効化する必要があります。
class MyApp < Sinatra::Base configure :production, :development do enable :logging end end
MIMEタイプ(Mime Types)¶ ↑
send_file
か静的ファイルを使う時、SinatraがMIMEタイプを理解できない場合があります。その時は mime_type
を使ってファイル拡張子毎に登録してください。
configure do mime_type :foo, 'text/foo' end
これはcontent_type
ヘルパーで利用することができます:
get '/' do content_type :foo "foo foo foo" end
URLの生成¶ ↑
URLを生成するためにはurl
ヘルパーメソッドが使えます。Hamlではこのようにします。
これはリバースプロキシおよびRackルーティングを、それらがあれば考慮に入れます。
このメソッドにはto
というエイリアスがあります(以下の例を参照)。
ブラウザリダイレクト(Browser Redirect)¶ ↑
redirect
ヘルパーメソッドを使うことで、ブラウザをリダイレクトさせることができます。
get '/foo' do redirect to('/bar') end
他に追加されるパラメータは、halt
に渡される引数と同様に取り扱われます。
redirect to('/bar'), 303 redirect 'https://www.google.com/', 'wrong place, buddy'
また、redirect back
を使えば、簡単にユーザが来たページへ戻るリダイレクトを作れます。
get '/foo' do "<a href='/bar'>do something</a>" end get '/bar' do do_something redirect back end
redirectに引数を渡すには、それをクエリーに追加するか、
redirect to('/bar?sum=42')
または、セッションを使います。
enable :sessions get '/foo' do session[:secret] = 'foo' redirect to('/bar') end get '/bar' do session[:secret] end
キャッシュ制御(Cache Control)¶ ↑
ヘッダを正しく設定することが、適切なHTTPキャッシングのための基礎となります。
キャッシュ制御ヘッダ(Cache-Control header)は、次のように簡単に設定できます。
get '/' do cache_control :public "キャッシュしました!" end
ヒント: キャッシングをbeforeフィルタ内で設定します。
before do cache_control :public, :must_revalidate, :max_age => 60 end
expires
ヘルパーを対応するヘッダに使っている場合は、キャッシュ制御は自動で設定されます。
before do expires 500, :public, :must_revalidate end
キャッシュを適切に使うために、etag
またはlast_modified
を使うことを検討してください。これらのヘルパーを、重い仕事をさせる 前 に呼ぶことを推奨します。そうすれば、クライアントが既にキャッシュに最新版を持っている場合はレスポンスを直ちに破棄するようになります。
get '/article/:id' do @article = Article.find params['id'] last_modified @article.updated_at etag @article.sha1 erb :article end
また、weak ETagを使うこともできます。
etag @article.sha1, :weak
これらのヘルパーは、キャッシングをしてくれませんが、必要な情報をキャッシュに与えてくれます。もし手早いリバースプロキシキャッシングの解決策をお探しなら、 rack-cacheを試してください。
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
オプションを渡すことで変更できます。
get '/create' do etag '', :new_resource => true Article.create erb :new_article end
ここでもWeak ETagを使いたい場合は、:kind
オプションを渡してください。
etag '', :new_resource => true, :kind => :weak
ファイルの送信¶ ↑
ファイルを送信するには、send_file
ヘルパーメソッドを使います。
get '/' do send_file 'foo.png' end
これはオプションを取ることもできます。
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
メソッドを通じてリクエストレベル(フィルタ、ルーティング、エラーハンドラ)からアクセスすることができます。
# アプリケーションが 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
などのオプションは次のように利用することもできます。
before { request.path_info = "/" } get "/" do "全てのリクエストはここに来る" end
request.body
はIOまたはStringIOのオブジェクトです。
post "/api" do request.body.rewind # 既に読まれているときのため data = JSON.parse request.body.read "Hello #{data['name']}!" end
アタッチメント(Attachments)¶ ↑
attachment
ヘルパーを使って、レスポンスがブラウザに表示されるのではなく、ディスクに保存されることをブラウザに対し通知することができます。
get '/' do attachment "保存しました!" end
ファイル名を渡すこともできます。
get '/' do attachment "info.txt" "保存しました!" end
日付と時刻の取り扱い¶ ↑
Sinatraはtime_for
ヘルパーメソッドを提供しており、それは与えられた値からTimeオブジェクトを生成します。これはまたDateTime
、Date
および類似のクラスを変換できます。
get '/' do pass if Time.now > time_for('Dec 23, 2012') "まだ時間がある" end
このメソッドは、expires
、last_modified
といった種類のものの内部で使われています。そのため、アプリケーションにおいて、time_for
をオーバーライドすることでそれらのメソッドの挙動を簡単に拡張できます。
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
ヘルパーは、レンダリングのためのテンプレートファイルを見つけるために使われます。
find_template settings.views, 'foo', Tilt[:haml] do |file| puts "could be #{file}" end
この例はあまり有益ではありません。しかし、このメソッドを、独自の探索機構で働くようオーバーライドするなら有益になります。例えば、複数のビューディレクトリを使えるようにしたいときがあります。
set :views, ['views', 'templates'] helpers do def find_template(views, name, engine, &block) Array(views).each { |v| super(v, name, engine, &block) } end end
他の例としては、異なるエンジン用の異なるディレクトリを使う場合です。
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回だけ実行されます。
configure do # 1つのオプションをセット set :option, 'value' # 複数のオプションをセット set :a => 1, :b => 2 # `set :option, true`と同じ enable :option # `set :option, false`と同じ disable :option # ブロックを使って動的な設定をすることもできます。 set(:css_dir) { File.join(views, 'css') } end
環境設定(APP_ENV
環境変数)が:production
に設定されている時だけ実行する方法:
configure :production do ... end
環境設定が:production
か:test
に設定されている時だけ実行する方法:
configure :production, :test do ... end
設定したオプションにはsettings
からアクセスできます:
configure do set :foo, 'bar' end get '/' do settings.foo? # => true settings.foo # => 'bar' ... end
攻撃防御に対する設定¶ ↑
SinatraはRack::Protectionを使用することで、アプリケーションを一般的な日和見的攻撃から守っています。これは簡単に無効化できます(が、アプリケーションに大量の一般的な脆弱性を埋め込むことになってしまいます)。
disable :protection
ある1つの防御を無効にするには、protection
にハッシュでオプションを指定します。
set :protection, :except => :path_traversal
配列を渡すことで、複数の防御を無効にすることもできます。
set :protection, :except => [:path_traversal, :session_hijacking]
デフォルトでSinatraは、:sessions
が有効になっている場合、セッションベースの防御だけを設定します。しかし、自身でセッションを設定したい場合があります。その場合は、:session
オプションを渡すことにより、セッションベースの防御を設定することができます。
use Rack::Session::Pool set :protection, :session => true
利用可能な設定¶ ↑
- absolute_redirects
- 無効のとき、Sinatraは相対リダイレクトを許容するが、RFC 2616 (HTTP 1.1)は絶対リダイレクトのみを許容するので、これには準拠しなくなる。
- アプリケーションが、適切に設定されていないリバースプロキシの裏で走っている場合は有効。ノート: urlヘルパーは、第2引数にfalseを渡さない限り、依然として絶対URLを生成する。
- デフォルトは無効。
- add_charset
- Mimeタイプ content_typeヘルパーが自動的にキャラクタセット情報をここに追加する。このオプションは書き換えるのではなく、値を追加するようにすること。 settings.add_charset << "application/foobar"
- app_file
- メインのアプリケーションファイルのパスであり、プロジェクトのルート、viewsおよびpublicフォルダを見つけるために使われる。
- bind
- バインドするIPアドレス(デフォルト: `environment`がdevelopmentにセットされているときは、0.0.0.0 または localhost)。ビルトインサーバでのみ使われる。
- default_encoding
- 不明なときに仮定されるエンコーディング(デフォルトは"utf-8")。
- dump_errors
- ログにおけるエラーの表示。
- environment
- 現在の環境。デフォルトはENV['APP_ENV']、それが無い場合は"development"。
- logging
- ロガーの使用。
- lock
- 各リクエスト周りのロックの配置で、Rubyプロセスごとにリクエスト処理を並行して走らせるようにする。
- アプリケーションがスレッドセーフでなければ有効。デフォルトは無効。
- method_override
- put/deleteフォームを、それらをサポートしないブラウザで使えるように_methodのおまじないを使えるようにする。
- port
- 待ち受けポート。ビルトインサーバのみで有効。
- prefixed_redirects
- 絶対パスが与えられていないときに、リダイレクトにrequest.script_nameを挿入するか否かの設定。これによりredirect '/foo'は、redirect to('/foo')のように振る舞う。デフォルトは無効。
- protection
- Web攻撃防御を有効にするか否かの設定。上述の攻撃防御の項を参照。
- public_dir
- public_folderのエイリアス。以下を参照。
- public_folder
- publicファイルが提供されるディレクトリのパス。静的ファイルの提供が有効になっている場合にのみ使われる (以下のstatic設定を参照)。設定されていない場合、app_file設定から推定。
- reload_templates
- リクエスト間でテンプレートを再ロードするか否かの設定。developmentモードでは有効。
- root
- プロジェクトのルートディレクトリのパス。設定されていない場合、app_file設定から推定。
- raise_errors
- 例外発生の設定(アプリケーションは止まる)。environmentが"test"に設定されているときはデフォルトは有効。それ以外は無効。
- run
- 有効のとき、SinatraがWebサーバの起動を取り扱う。rackupまたは他の手段を使うときは有効にしないこと。
- running
- ビルトインサーバが稼働中か?この設定を変更しないこと!
- server
- ビルトインサーバとして使用するサーバまたはサーバ群の指定。指定順位は優先度を表し、デフォルトはRuby実装に依存。
- sessions
- Rack::Session::Cookieを使ったクッキーベースのセッションサポートの有効化。詳しくは、'セッションの使用'の項を参照のこと。
- show_exceptions
- 例外発生時にブラウザにスタックトレースを表示する。environmentが"development"に設定されているときは、デフォルトで有効。それ以外は無効。
- また、:after_handlerをセットすることができ、これにより、ブラウザにスタックトレースを表示する前に、アプリケーション固有のエラーハンドリングを起動させられる。
- static
- Sinatraが静的ファイルの提供を取り扱うかの設定。
- その取り扱いができるサーバを使う場合は無効。
- 無効化でパフォーマンスは改善する
- クラッシックスタイルではデフォルトで有効。モジュラースタイルでは無効。
- static_cache_control
- Sinatraが静的ファイルを提供するときこれをセットして、レスポンスにCache-Controlヘッダを追加するようにする。cache_controlヘルパーを使うこと。デフォルトは無効。
- 複数の値をセットするときは明示的に配列を使う: set :static_cache_control, [:public, :max_age => 300]
- threaded
- trueに設定されているときは、Thinにリクエストを処理するためにEventMachine.deferを使うことを通知する。
- views
- ビューディレクトリのパス。設定されていない場合、app_file設定から推定する。
- x_cascade
- マッチするルーティングが無い場合に、X-Cascadeヘッダをセットするか否かの設定。デフォルトはtrue。
環境設定(Environments)¶ ↑
3種類の既定環境、"development"
、"production"
および"test"
があります。環境は、APP_ENV
環境変数を通して設定できます。デフォルト値は、"development"
です。"development"
環境において、すべてのテンプレートは、各リクエスト間で再ロードされ、そして、特別のnot_found
およびerror
ハンドラがブラウザにスタックトレースを表示します。"production"
および"test"
環境においては、テンプレートはデフォルトでキャッシュされます。
異なる環境を走らせるには、APP_ENV
環境変数を設定します。
APP_ENV=production ruby my_app.rb
既定メソッド、development?
、test?
およびproduction?
を、現在の環境設定を確認するために使えます。
get '/' do if settings.development? "development!" else "not development!" end end
エラーハンドリング(Error Handling)¶ ↑
エラーハンドラはルーティングおよびbeforeフィルタと同じコンテキストで実行されます。すなわちこれは、haml
、erb
、halt
といった便利なものが全て使えることを意味します。
未検出(Not Found)¶ ↑
Sinatra::NotFound
例外が発生したとき、またはレスポンスのステータスコードが404のときに、not_found
ハンドラが発動します。
not_found do 'ファイルが存在しません' end
エラー(Error)¶ ↑
error
ハンドラはルーティングブロックまたはフィルタ内で例外が発生したときはいつでも発動します。 しかし、環境設定がdevelopmentの場合は:after_handler
を設定している場合のみ発動するようになります。
set :show_exceptions, :after_handler
例外オブジェクトはRack変数sinatra.error
から取得できます。
error do 'エラーが発生しました。 - ' + env['sinatra.error'].message end
エラーをカスタマイズする場合は、
error MyCustomError do 'エラーメッセージ...' + env['sinatra.error'].message end
と書いておいて、下記のように呼び出します。
get '/' do raise MyCustomError, '何かがまずかったようです' end
そうするとこうなります。
エラーメッセージ... 何かがまずかったようです
あるいは、ステータスコードに対応するエラーハンドラを設定することもできます。
error 403 do 'Access forbidden' end get '/secret' do 403 end
範囲指定もできます。
error 400..510 do 'Boom' end
Sinatraを開発環境の下で実行している場合は、特別なnot_found
およびerror
ハンドラが導入され、これは親切なスタックトレースと追加のデバッギング情報をブラウザに表示します。
Rackミドルウェア(Rack Middleware)¶ ↑
SinatraはRuby製Webフレームワークのミニマルな標準的インタフェースである{Rack}[https://rack.github.io/]上に構築されています。アプリケーションデベロッパーにとってRackにおける最も興味深い機能は、「ミドルウェア(middleware)」をサポートしていることであり、これは、サーバとアプリケーションとの間に置かれ、HTTPリクエスト/レスポンスを監視および/または操作することで、各種の汎用的機能を提供するコンポーネントです。
Sinatraはトップレベルのuse
メソッドを通して、Rackミドルウェアパイプラインの構築を楽にします。
require 'sinatra' require 'my_custom_middleware' use Rack::Lint use MyCustomMiddleware get '/hello' do 'Hello World' end
use
の文法は、{Rack::Builder}[http://www.rubydoc.info/github/rack/rack/master/Rack/Builder]DSLで定義されているそれ(rackupファイルで最もよく使われる)と同じです。例えば use
メソッドは複数の引数、そしてブロックも取ることができます。
use Rack::Auth::Basic do |username, password| username == 'admin' && password == 'secret' end
Rackは、ロギング、デバッギング、URLルーティング、認証、セッション管理など、多様な標準的ミドルウェアを共に配布されています。Sinatraはその多くのコンポーネントを自動で使うよう基本設定されているため、通常、それらをuse
で明示的に指定する必要はありません。
便利なミドルウェアを以下で見つけられます。
rack、 rack-contrib、 またはRack wiki。
テスト(Testing)¶ ↑
SinatraでのテストはRackベースのテストライブラリまたはフレームワークを使って書くことができます。Rack::Testをお薦めします。
require 'my_sinatra_app' require 'minitest/autorun' require 'rack/test' class MyAppTest < Minitest::Test include Rack::Test::Methods def app Sinatra::Application end def test_my_default get '/' assert_equal 'Hello World!', last_response.body end def test_with_params get '/meet', :name => 'Frank' assert_equal 'Hello Frank!', last_response.body end def test_with_user_agent get '/', {}, 'HTTP_USER_AGENT' => 'Songbird' assert_equal "Songbirdを使ってます!", last_response.body end end
ノート: モジュラースタイルでSinatraを使う場合は、上記Sinatra::Application
をアプリケーションのクラス名に置き換えてください。
Sinatra::Base - ミドルウェア、ライブラリおよびモジュラーアプリ¶ ↑
軽量なアプリケーションであれば、トップレベルでアプリケーションを定義していくことはうまくいきますが、再利用性可能なコンポーネント、例えばRackミドルウェア、RailsのMetal、サーバコンポーネントを含むシンプルなライブラリ、あるいはSinatraの拡張プログラムを構築するような場合、これは無視できない欠点を持つものとなります。トップレベルは、軽量なアプリケーションのスタイルにおける設定(例えば、単一のアプリケーションファイル、./public
および./views
ディレクトリ、ロギング、例外詳細ページなど)を仮定しています。そこでSinatra::Base
の出番です。
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(英語)をご覧ください。
もしもクラシックスタイルと同じような挙動のアプリケーションをトップレベルで定義させる必要があれば、Sinatra::Application
をサブクラス化させてください。
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!
を使って開始させる二種類のやり方があります。
# my_app.rb require 'sinatra/base' class MyApp < Sinatra::Base # ... アプリケーションのコードを書く ... # Rubyファイルが直接実行されたらサーバを立ち上げる run! if app_file == $0 end
として、次のように起動するか、
ruby my_app.rb
または、Rackハンドラを使えるようにするconfig.ru
ファイルを書いて、
# config.ru (rackupで起動) require './my_app' run MyApp
起動します。
rackup -p 4567
config.ruを用いたクラッシックスタイルアプリケーションの使用¶ ↑
アプリケーションファイルと、
# app.rb require 'sinatra' get '/' do 'Hello world!' end
対応するconfig.ru
を書きます。
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/…)が用いられるでしょう。
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 "アクセスは拒否されました。<a href='/login'>ログイン</a>してください。" end end get('/') { "Hello #{session['user_name']}." } end
動的なアプリケーションの生成¶ ↑
新しいアプリケーションを実行時に、定数に割り当てることなく生成したくなる場合があるでしょう。Sinatra.new
を使えばそれができます。
require 'sinatra/base' my_app = Sinatra.new { get('/') { "hi" } } my_app.run!
これは省略できる引数として、それが継承するアプリケーションを取ります。
# 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をミドルウェアとして利用することを極めて簡単にします。
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
によって作られたオプションはクラスレベルのメソッドです。
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
ヘルパーによってアプリケーションスコープにアクセスすることができます。
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されています。
コマンドライン¶ ↑
Sinatraアプリケーションは直接実行できます。
ruby myapp.rb [-h] [-x] [-e ENVIRONMENT] [-p PORT] [-o HOST] [-s HANDLER]
オプション:
-h # ヘルプ -p # ポート指定(デフォルトは4567) -o # ホスト指定(デフォルトは0.0.0.0) -e # 環境を指定 (デフォルトはdevelopment) -s # rackserver/handlerを指定 (デフォルトはthin) -x # mutex lockを付ける (デフォルトはoff)
マルチスレッド¶ ↑
このStackOverflow のKonstantinによる回答を言い換えています。
Sinatraでは同時実行モデルを負わせることはできませんが、根本的な部分であるThinやPuma、WebrickのようなRackハンドラ(サーバー)部分に委ねることができます。 Sinatra自身はスレッドセーフであり、もしRackハンドラが同時実行モデルのスレッドを使用していても問題はありません。 つまり、これはサーバーを起動させる時、特定のRackハンドラに対して正しい起動処理を特定することが出来ます。 この例はThinサーバーをマルチスレッドで起動する方法のデモです。
# app.rb require 'sinatra/base' class App < Sinatra::Base get '/' do "Hello, World" end end App.run!
サーバーを開始するコマンドです。
thin --threaded start
必要環境¶ ↑
次のRubyバージョンが公式にサポートされています。
- Ruby 1.8.7
- 1.8.7は完全にサポートされていますが、特にそれでなければならないという理由がないのであれば、アップグレードまたはJRubyまたはRubiniusへの移行を薦めます。1.8.7のサポートがSinatra 2.0の前に終わることはないでしょう。Ruby 1.8.6はサポート対象外です。
- Ruby 1.9.2
- 1.9.2は完全にサポートされています。1.9.2p0は、Sinatraを起動したときにセグメントフォルトを引き起こすことが分かっているので、使わないでください。公式なサポートは、少なくともSinatra 1.5のリリースまでは続きます。
- Ruby 1.9.3
- 1.9.3は完全にサポート、そして推奨されています。以前のバージョンからの1.9.3への移行は全セッションを無効にする点、覚えておいてください。
- Ruby 2.0.0
- 2.0.0は完全にサポート、そして推奨されています。現在、その公式サポートを終了する計画はありません。
- Rubinius
- Rubiniusは公式にサポートされています(Rubinius >= 2.x)。 gem install pumaすることが推奨されています。
- JRuby
- JRubyの最新安定版が公式にサポートされています。JRubyでC拡張を使うことは推奨されていません。 gem install trinidadすることが推奨されています。
開発チームは常に最新となるRubyバージョンに注視しています。
次のRuby実装は公式にはサポートされていませんが、Sinatraが起動すると報告されています。
-
JRubyとRubiniusの古いバージョン
-
Ruby Enterprise Edition
-
MacRuby, Maglev, IronRuby
-
Ruby 1.9.0と1.9.1 (これらの使用はお薦めしません)
公式サポートをしないという意味は、問題がそこだけで起こり、サポートされているプラットフォーム上では起きない場合に、開発チームはそれはこちら側の問題ではないとみなすということです。
開発チームはまた、ruby-head(最新となる2.1.0)に対しCIを実行していますが、それが一貫して動くようになるまで何も保証しません。2.1.0が完全にサポートされればその限りではありません。
Sinatraは、利用するRuby実装がサポートしているオペレーティングシステム上なら動作するはずです。
MacRubyを使う場合は、gem install control_tower
してください。
Sinatraは現在、Cardinal、SmallRuby、BlueRubyまたは1.8.7以前のバージョンのRuby上では動作しません。
最新開発版¶ ↑
Sinatraの最新開発版のコードを使いたい場合は、マスターブランチに対してアプリケーションを走らせて構いません。ある程度安定しています。また、適宜プレリリース版gemをpushしているので、
gem install sinatra --pre
すれば、最新の機能のいくつかを利用できます。
Bundlerを使う場合¶ ↑
最新のSinatraでアプリケーションを動作させたい場合には、Bundlerを使うのがお薦めのやり方です。
まず、Bundlerがなければそれをインストールします。
gem install bundler
そして、プロジェクトのディレクトリで、Gemfile
を作ります。
source 'https://rubygems.org' gem 'sinatra', :github => "sinatra/sinatra" # 他の依存ライブラリ gem 'haml' # Hamlを使う場合 gem 'activerecord', '~> 3.0' # ActiveRecord 3.xが必要かもしれません
ノート: Gemfile
にアプリケーションの依存ライブラリのすべてを並べる必要があります。しかし、Sinatraが直接依存するもの(RackおよびTile)はBundlerによって自動的に取り込まれ、追加されます。
これで、以下のようにしてアプリケーションを起動することができます。
bundle exec ruby myapp.rb
直接組み込む場合¶ ↑
ローカルにクローンを作って、sinatra/lib
ディレクトリを$LOAD_PATH
に追加してアプリケーションを起動します。
cd myapp git clone git://github.com/sinatra/sinatra.git ruby -I sinatra/lib myapp.rb
追ってSinatraのソースを更新する方法。
cd myapp/sinatra git pull
グローバル環境にインストールする場合¶ ↑
Sinatraのgemを自身でビルドすることもできます。
git clone git://github.com/sinatra/sinatra.git cd sinatra rake sinatra.gemspec rake install
gemをルートとしてインストールする場合は、最後のステップはこうなります。
sudo rake install
バージョニング(Versioning)¶ ↑
Sinatraは、{Semantic Versioning}[https://semver.org/]におけるSemVerおよびSemVerTagの両方に準拠しています。
参考文献¶ ↑
-
プロジェクトサイト - ドキュメント、ニュース、他のリソースへのリンクがあります。
-
プロジェクトに参加(貢献)する - バグレポート パッチの送信、サポートなど
-
Sinatra Book クックブック、チュートリアル
-
Sinatra Recipes コミュニティによるレシピ集
-
www.rubydoc.info/上のAPIドキュメント: 最新版(latest release)用}[http://www.rubydoc.info/gems/sinatra]または{現在のHEAD用