Grape

簡単な API サーバーを Ruby で作ろうと思って Rails じゃ大げさすぎるし Sinatra かなーと思ってたら Grape というのがあったので試してみてる。

自分用のメモなので、ちゃんと知りたい人は https://github.com/ruby-grape/grape/blob/master/README.md を読みましょう。

基本

Gemfile を作って、

source 'https://rubygems.org'

gem 'grape'

bundle install して、

api.rb という名前でファイルを作って(名前はなんでもいい)、

require 'grape'

class API < Grape::API
  get :hoge do
    'GET'
  end

  post :hoge do
    'POST'
  end

  put :hoge do
    'PUT'
  end

  delete :hoge do
    'DELETE'
  end
end

config.ru を作って、

require_relative 'api'

run API

rackup --port 10080 コマンドで実行。1 こんな感じ。

~% curl -X GET -D- -d '' http://127.0.0.1:10080/hoge
HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 3
Server: WEBrick/1.6.0 (Ruby/2.7.0/2019-12-25)
Date: Mon, 24 Feb 2020 13:31:00 GMT
Connection: Keep-Alive

GET

~% curl -X POST -D- -d '' http://127.0.0.1:10080/hoge
HTTP/1.1 201 Created
Content-Type: text/plain
Content-Length: 4
Server: WEBrick/1.6.0 (Ruby/2.7.0/2019-12-25)
Date: Mon, 24 Feb 2020 13:31:06 GMT
Connection: Keep-Alive

POST

~% curl -X PUT -D- -d '' http://127.0.0.1:10080/hoge
HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 3
Server: WEBrick/1.6.0 (Ruby/2.7.0/2019-12-25)
Date: Mon, 24 Feb 2020 13:31:08 GMT
Connection: Keep-Alive

PUT

~% curl -X DELETE -D- -d '' http://127.0.0.1:10080/hoge
HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 6
Server: WEBrick/1.6.0 (Ruby/2.7.0/2019-12-25)
Date: Mon, 24 Feb 2020 13:31:12 GMT
Connection: Keep-Alive

DELETE

メソッドオーバーライド

_method=DELETE つけて POST しても DELETE と見なしてくれないけど、

~% curl -X POST -d '_method=DELETE' http://127.0.0.1:10080/hoge
POST

config.ru で Rack::MethodOverride を使用するようにしておくと、

require_relative 'api'

use Rack::MethodOverride
run API

DELETE と見なしてくれる。

~% curl -X POST -d '_method=DELETE' http://127.0.0.1:10080/hoge
DELETE

ネームスペース

namespace を使ってこんな風にも書ける。

require 'grape'

class API < Grape::API
  namespace :hoge do
    get do
      'GET'
    end

    post do
      'POST'
    end

    put do
      'PUT'
    end

    delete do
      'DELETE'
    end
  end
end

リクエスト

リクエストのヘッダは headers、パラメータは params で Hash としてアクセスできる。

class API < Grape::API
  get :hoge do
    headers.inspect + "\n" + params.inspect + "\n"
  end
end
~% curl 'http://127.0.0.1:10080/hoge?a=123&b=xyz'
{"Host"=>"127.0.0.1:10080", "User-Agent"=>"curl/7.65.3", "Accept"=>"*/*", "Version"=>"HTTP/1.1"}
{"a"=>"123", "b"=>"xyz"}

パラメータは params で定義しておくとバリデーション&変換してくれる。 パス中に :識別子 で含めることもできる。

class API < Grape::API
  params do
    requires :id, type: Integer
    requires :s,  type: String
    optional :p,  type: Date
  end
  get 'hoge/:id' do
    params
  end
end
~% curl 'http://127.0.0.1:10080/hoge/'
404 Not Found
~% curl 'http://127.0.0.1:10080/hoge/abc'
id is invalid, s is missing
~% curl 'http://127.0.0.1:10080/hoge/123'
s is missing
~% curl 'http://127.0.0.1:10080/hoge/123?s=hoge'
{"s"=>"hoge", "id"=>123}
~% curl 'http://127.0.0.1:10080/hoge/123?s=hoge&p=abc'
p is invalid
~% curl 'http://127.0.0.1:10080/hoge/123?s=hoge&p=2020-02-25'
{"s"=>"hoge", "p"=>Tue, 25 Feb 2020, "id"=>123}

正規表現の指定も可。

class API < Grape::API
  params do
    optional :ipv4, type: String, regexp: /\A\d+\.\d+\.\d+\.\d+\z/
  end
  get 'hoge' do
    params[:ipv4]
  end
end

ただし、. を含むパラメータはパス中には指定できない。

class API < Grape::API
  params do
    requires :ipv4, type: String, regexp: /\A\d+\.\d+\.\d+\.\d+\z/
  end
  get 'hoge/:ipv4' do
    params[:ipv4]
  end
end
~% curl 'http://127.0.0.1:10080/hoge/1.2.3.4'
404 Not Found

パス中のパラメータの正規表現を指定すればOK。この場合は \A\z は不要らしい。

class API < Grape::API
  get 'hoge/:ipv4', requirements: {ipv4: /\d+\.\d+\.\d+\.\d+/ } do
    params[:ipv4]
  end
end
~% curl 'http://127.0.0.1:10080/hoge/1.2.3.4'
1.2.3.4

レスポンス

ブロックを評価した結果のオブジェクトがレスポンスデータになる。

class API < Grape::API
  get :hoge do
    {abc: 123, xyz: 789}
  end
end
~% curl -D- http://127.0.0.1:10080/hoge
HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 25
Server: WEBrick/1.6.0 (Ruby/2.7.0/2019-12-25)
Date: Mon, 24 Feb 2020 14:04:34 GMT
Connection: Keep-Alive

{:abc=>123, :xyz=>"hoge"}

リクエストの拡張子で Content-Type を指定することでそれっぽく自動変換してくれる。

~% curl -D- http://127.0.0.1:10080/hoge.txt
HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 25
Server: WEBrick/1.6.0 (Ruby/2.7.0/2019-12-25)
Date: Mon, 24 Feb 2020 14:04:36 GMT
Connection: Keep-Alive

{:abc=>123, :xyz=>"hoge"}

~% curl -D- http://127.0.0.1:10080/hoge.json
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 24
Server: WEBrick/1.6.0 (Ruby/2.7.0/2019-12-25)
Date: Mon, 24 Feb 2020 14:04:38 GMT
Connection: Keep-Alive

{"abc":123,"xyz":"hoge"}

~% curl -D- http://127.0.0.1:10080/hoge.xml
HTTP/1.1 200 OK
Content-Type: application/xml
Content-Length: 104
Server: WEBrick/1.6.0 (Ruby/2.7.0/2019-12-25)
Date: Mon, 24 Feb 2020 14:04:40 GMT
Connection: Keep-Alive

<?xml version="1.0" encoding="UTF-8"?>
<hash>
  <abc type="integer">123</abc>
  <xyz>hoge</xyz>
</hash>

レスポンスステータスは status で指定する。

class API < Grape::API
  get :hoge do
    status 400
    'なんかおかしいよ'
  end
end
~% curl -D- http://127.0.0.1:10080/hoge
HTTP/1.1 400 Bad Request
Content-Type: text/plain
Content-Length: 24
Server: WEBrick/1.6.0 (Ruby/2.7.0/2019-12-25)
Date: Mon, 24 Feb 2020 14:14:50 GMT
Connection: Keep-Alive

なんかおかしいよ

バージョニング

バージョニングができるのが特徴らしい。

require 'grape'

class API1 < Grape::API
  version 'v1'
  get :hoge do
    'Version 1'
  end
end
require 'grape'

class API2 < Grape::API
  version 'v2'
  get :hoge do
    'Version 2'
  end
end
require_relative 'api1'
require_relative 'api2'

use Rack::MethodOverride
run Rack::Cascade.new [API1, API2]

こんな感じにしとくと、URL中のパスでバージョンを指定できるようになる。

~% curl http://127.0.0.1:10080/v1/hoge
Version 1
~% curl http://127.0.0.1:10080/v2/hoge
Version 2

デフォルトはパスだけど、クエリパラメータで指定するようにすることもできるみたい。

ドキュメント

grape-swagger というのを使うと Swagger/OpenAPI 的にうまいことやってくれるらしい。そのうち調べる。


なんとなくよさそうなのでちょっと使ってみようかなーっと。


  1. --port 10080 をつけているのは、デフォルトの 9292 だと Emacs の edit server と被るため。