write ahead log

ロールフォワード用

Sinatraを使う

久しぶりにRubyを触っているこの頃ですが, Railsはやはり覚える事が多いのでまだまだSinatraの方が私には楽です.

遠い記憶になっているので少しまとめました.

基本的にClassicスタイルで書いていきます.

書いてて思ったのは大体は公式に記載があるということです.

基本

ルーティング

普通のルーティング

HTTPのメソッド名 + ルート + BlockというSinatraが有名になった美しい構文.

Rubyらしく最後に評価された式の値でレスポンスになります.

get '/' do
  'ここは/にgetリクエストを送ったときに呼び出される場所'
end

post '/' do
  ...
end

put '/' do
  ...
end

delete '/' do
  ...
end

URLパラメータでのルーティング

URL内の

:変数名

がパラメータになります.

require 'sinatra'

get '/:hoge' do      #=> localhost:4567/fooへアクセス  -> fooと表示
  params[:hoge]
end

公式を読むと他にも条件付きマッチなどかなり色々できます.

リダイレクト

URL指定
get '/' do
  redirect 'https://www.google.com'
end
パス指定
get '/' do
  redirect to('/topath')
end
元いた場所へ戻る
get '/back' do
  redirect back
end

変わり種

正規表現でのルーティングやhalt/passというメソッドが面白いです.

halt

haltは途中で処理を止めてレスポンスを返します.

以下の例ではクエリパラメータhiにhiが渡された時だけhiと表示します.

require 'sinatra'

get '/' do
  halt "hi" if params['hi'] == "hi"
  "Hello"
end

他にもステータスコードを渡したりもできるようです.

正規表現でのルーティングとpass

ルーティングのURLには正規表現が使えます.

また, passメソッドを使うと他のマッチするルートへ移ります.

以下の例ではgreeting以下のhiにアクセスする時以外は全て2番目のルートで処理しますが, greeting/hiへのアクセス時にクエリパラメータでhi=hiが設定されているときはやはり2番目のルートで処理します.

(例が悪かったかな)

require 'sinatra'

get '/greeting/hi' do
  pass if params['hi'] == 'hi'
  "Hi"
end

get '/greeting/*' do
  "Hello"
end

パラメータ(params)

GetパラメータもPostパラメータもURLパラメータも以下だけでOKです.

params[:パラメータ名のシンボル]

ルーティングのブロックから取ったりもできるようですが.

外部テンプレート(Views)

viewsディレクトリを作っておくといい感じにしてくれます.
(erbの例)

.
├── server.rb
└── views
    └── index.erb

Rubyコードからはerbメソッドで拡張子抜きのファイル名のシンボルを呼び出すとviews以下のerbが呼ばれます.

require 'sinatra'

get '/' do
  erb :index
end

erbはこんな感じで普通に書きます.

<h1>Hello</h1>

<%= DateTime.now.strftime('%D %X') %>

共通レイアウト

共通レイアウトをまとめるlayoutも使えます.

viewsディレクトリの中にlayout.erbで配置するだけです.

.
├── server.rb
└── views
    ├── index.erb
    └── layout.erb

layout.erbの中ではyieldで個々のテンプレートへ移譲します.

<!DOCTYPE html>
<html>
    <head>
       <title>layout Sample</title>
   </head>
    <body>
        <%= yield %>
    </body>
</html>

Rubyのコードでテンプレートを呼び出すときにlayoutの有無を選択できるようです.

get '/' do
  erb :index, layout: false
end

JSONを返す

content_typeを明示的に指定する必要がある.

require 'sinatra'
require 'json'

get '/' do
  content_type :json
  data = { message: 'msg' }
  data.to_json
end

ヘルパーメソッド

ビューで使いたいメソッドはhelpersメソッドのブロックに書いておくとテンプレート内から呼び出せるようになります.

require 'sinatra'

get '/' do
  erb :index
end

helpers do
  def help
    "Help!"
  end
end

HTMLはこんな感じ.

<%= help %>

これで"Help!"と表示されます.

静的ファイル配信

デフォルトでpublicディレクトリが公開されます.

こんな配置にしておけば

.
├── public
│   └── css
│       └── style.css
├── server.rb
└── views
    └── index.erb

HTMLからはこれで呼び出せます.

<html>
    <head>
       <link rel="stylesheet" href="css/style.css">
   </head>
    <body>
    Link style
    </body>
</html>

ファイルアップロード

params[:fileのinput名]にファイルに関する情報が入っているのでこれを使います.

params[:fileのinput名][:filename] # ファイル名
params[:fileのinput名][:tempfile] # ファイルそのもの

Rubyのコードはこんな感じ.

require 'sinatra'

get '/' do
  erb :index
end

post '/upload' do
  if params[:file1]
    # カレントのimagesディレクトリへ保存する
    path = "images/#{params[:file1][:filename]}"
    File.open(path, 'wb') { |f|
      f.write params[:file1][:tempfile].read
    }
  end
  redirect back
end

indexのアップロードフォームも適当に.
enctype指定は忘れがち.

<form action="/upload" method="POST" enctype="multipart/form-data">
    <input type="file" name="file1">
    <input type="submit">
</form>

ファイルダウンロード

send_fileにカレントからのファイルパスを書けば送信できます.

typeでmimeも指定できる.

get '/' do
  send_file 'images/img.jpg', type: :jpg
end

フィルタ

beforeとafterメソッドがあります. そのままですね.

before do
  ...
end

after do
  ...
end

条件付きマッチもできます.

before '/loggerarea/*' do
  logger.info "logging"
end

DB接続

Railsの様に永続化機能がフレームワークで用意されているわけではないので, 自由に選べます.

ActiveRecordの例が多いですが, 個人的にはそのレベルならRails使う方が良い気がするのでSequelが簡単かなと思います.

セキュリティ系

この話題は避けられない割にキリがないのですが, とりあえず最低限だけ.

XSS対策

helperにRackのescape_htmlの呼び出しを書いておくとviewでも使えるようになります.

helpers do
  def h(text)
    Rack::Utils.escape_html(text)
  end
end

erbなら以下.

<%= h "<script>alert()</script>" %>

Sessionを使う

戸惑うことはないでしょう.

# 有効化
enable :sessions

# 記録
session[:message] = "This line use session"

# 読出
session[:message]

CSRF対策

Rack::ProtectionへSinatra本体が依存しているので, 既に利用がマージされてるようです.

コードの先頭で呼び出せばさらに色々使えます.

# server.rb
require 'sinatra'
require 'rack/protection'   #ここ

use Rack::Protection        #ここ
enable :sessions

全機能使おうとするとsessionが有効でないと使えないので注意.

Basic認証

StackOverFlowまんまですが.

  def authorized?
    @auth ||=  Rack::Auth::Basic::Request.new(request.env)
    @auth.provided? && @auth.basic? && @auth.credentials && @auth.credentials == ["ユーザ名","パスワード"]
  end

  def protected!
    unless authorized?
      response['WWW-Authenticate'] = %(Basic realm="Restricted Area")
      throw(:halt, [401, "認証が必要です\n"])
    end
  end

これで以下のように使えます.

get '/' do
  "認証なし"
end

get '/auth' do
  protected!
  "認証あり"
end

古いですが, Sinatra RecipesにはDigest認証の方法についても記述がありました.

エラーページ

not foundはその名の通りnot_foundメソッドでキャッチできます.

他のエラーはerrorメソッドでキャッチできます.

errorメソッドは開発環境ではデバッグの表示が出るので, プロダクションに設定してやると分かりやすいです.

require 'sinatra'

configure do
  set :environment, :production
end

get '/' do
  "Hello"
  raise "not found"
end

not_found do
  "not found"
end

error do
  "sorry"
end

ちなみにREADMEを読むとerrorは引数もとれるらしいです.

ロギング

普通にRubyのloggerです.

get '/' do
  logger.info 'access to root'
end

Sinatra(というかRack?)がデフォルトで用意している出力先はenv['rack.logger']の様.

プロダクションではアプリケーションサーバが担当するのか?

テスト

Classicスタイルの場合. Modulerスタイルの場合はappのアプリケーションを変えてやればよいそうで.

require './app.rb'
require 'test/unit'
require 'rack/test'

class MyAppTest < Test::Unit::TestCase
  include Rack::Test::Methods

  def app
    Sinatra::Application
  end

  def test_root
    get '/'
    assert last_response.ok?
    assert_equal last_response.body, "Hello"
  end
end

デプロイ

長くなるので別記事にしよう.

便利ライブラリがあります.

GitHub - sinatra/sinatra-contrib

参考

公式

StackOverFlow - Display Sinatra Basic HTTP Auth On One Page Only

Rubyのlogger