猫Rails

ねこー🐈

rest-clientの使い方 まとめ

感想とか

注意点

  • これは自分用のまとめを公開したものです。ドキュメント/ソースコードを見ただけで試していないコードも多いので、参考程度に。

感想

  • ActiveResourceみたいな感じかと思ったら全然違った。普通のHTTPクライアントでRESTっぽさはあんまりない。
  • Faradayやhttp.rbのがシンプルで使いやすそう。まぁでも有名なgemならどれでも十分良い気がする。
  • 日本語情報は少ないので、情報探すならreadme -> ソースコードが良さそう。どちらも情報量多くて読みやすい。

pros

  • ドキュメントがしっかりしてる
  • RestClient::Resourceでリソースを表現できるのが便利そう

cons

  • 日本語情報は少なめ。海外では人気だけど日本ではあんまり人気ない?
  • 他のHTTPクライアントに比べると、APIがちょい複雑?getとpostでパラメータの取り方が変わったりするの、若干ややこしい気がする

リクエスト

RestClient.get: GET

# API
RestClient.get(url, headers={})

# 基本
RestClient.get 'http://example.com/resource'

# クエリパラメータ
# クエリパラメータはheadersのparamsで指定する。ややこしい。 -> 将来変更の可能性あり
RestClient.get 'http://example.com/resource', {params: {id: 50, 'foo' => 'bar'}}

# リクエストヘッダ
RestClient.get 'https://user:password@example.com/private/resource', {accept: :json}

RestClient.post: POST

  • urlencodedされる
# API
RestClient.post(url, payload, headers={})

# 基本
RestClient.post 'http://example.com/resource', {param1: 'one', nested: {param2: 'two'}}

# raw payloads
RestClient.post 'http://example.com/resource', 'the post body', :content_type => 'text/plain'
RestClient.post 'http://example.com/resource.pdf', File.read('my.pdf'), :content_type => 'application/pdf'

# json
RestClient.post "http://example.com/resource", {'x' => 1}.to_json, {content_type: :json, accept: :json}

# Multipart(ファイルあり)
RestClient.post '/data', :myfile => File.new("/path/to/image.jpg", 'rb')

# Multipart(ファイルなし)
RestClient.post '/data', {:foo => 'bar', :multipart => true}

RestClient.delete: DELETE

RestClient.delete 'http://example.com/resource'

RestClient.patch: PATCH

RestClient.put: PUT

RestClient.head: HEAD

RestClient.head('http://example.com').headers

RestClient.options: OPTIONS

RestClient.options('http://example.com')

RestClient::Request.execute: HTTPメソッド指定

使い方

  • .getとかではproxy等のオプションは利用できないっぽいので、そういう場合のみ.executeを使う
  • .getメソッド等は内部で.executeを利用している
# RestClient.get('http://example.com/resource') と同じ
RestClient::Request.execute(method: :get, url: 'http://example.com/resource')

# オプション指定1
RestClient::Request.execute(method: :get, url: 'http://example.com/resource',
                            timeout: 10)

# オプション指定2
RestClient::Request.execute(method: :get, url: 'http://example.com/resource',
                            ssl_ca_file: 'myca.pem',
                            ssl_ciphers: 'AESGCM:!aNULL')

# オプション指定3
RestClient::Request.execute(method: :delete, url: 'http://example.com/resource',
                            payload: 'foo', headers: {myheader: 'bar'})

# GET http://example.com/resource?foo=bar
# クエリパラメータはheadersのparamsに指定する
RestClient::Request.execute(method: :get, url: 'http://example.com/resource',
                            timeout: 10, headers: {params: {foo: 'bar'}})

必須の引数

:method: HTTPメソッド

:url: URL

オプションの引数

:headers: リクエストヘッダ

:cookies: クッキー

:user: ベーシック認証のuser

:password: ベーシック認証のpassword

:block_response: レスポンス時の処理

:raw_response: Responseの代わりにRawResponseを使う

:max_redirects: リダイレクト回数の上限値

  • デフォルト: 10

:proxy: プロキシのURI

  • RestClient.proxyよりもこっちが優先される

:verify_ssl: SSLのverifyの設定

  • デフォルト: OpenSSL::SSL::VERIFY_PEER

timeout: open_timeoutとread_timeoutを両方同時にセット

  • タイムアウト 参照

open_timeout: コネクションを開くまでに待つ最大秒数

read_timeout: データ読み込みまでに待つ最大秒数

:before_execution_proc: リクエスト前のフック(proc)

  • RestClient.add_before_execution_proc相当

:ssl_client_cert: クライアント証明書

  • Net::HTTP#cert=に対応

:ssl_client_key: クライアント証明書の秘密鍵

  • Net::HTTP#key=に対応

:ssl_ca_file: CA証明書ファイル

  • Net::HTTP#ca_fileに対応

:ssl_ca_path,: CA証明書ファイルを含むディレクトリ

  • Net::HTTP#ca_pathに対応

:ssl_cert_store: CA証明書を含む証明書ストア

  • Net::HTTP#cert_storeに対応

  • ca_file/ca_pathより細かく設定したい場合はコレを使う

:ssl_verify_callback: 検証をフィルタするコールバック

  • Net::HTTP#verify_callbackに対応

:ssl_verify_callback_warnings: trueなら警告

:ssl_version: SSLバージョン

  • Net::HTTP#ssl_version=に対応

:ssl_ciphers: 利用可能な共通鍵暗号の種類

  • Net::HTTP#ciphers=に対応

GETとPOSTでパラメータのとり方が違う

  • POST/PUT/PATCHはpayloadを取る。GET/DELETE/HEAD/OPTIONSはpayloadを取らない
  • パラメータの指定方法も異なるので注意する
# API
RestClient.get(url, headers={})
RestClient.post(url, payload, headers={})

# パラメータ指定
RestClient.get 'http://example.com', {params: {foo: "bar"}}       # GET http://example.com?foo=bar
RestClient.post 'http://example.com'', {}, {params: {foo: "bar"}} # POST http://example.com foo=bar
RestClient.post 'http://example.com', {foo: "bar"}                # POST http://example.com foo=bar(payload)

リクエストはステータスコードによって挙動が異なる

200系: responseオブジェクトを返す

300系: 自動でリダイレクト

400系/500系: 例外を投げる

  • rescueして個別に処理する。

レスポンス

response.code: ステータスコード

response = RestClient.get 'http://example.com/resource' # => <RestClient::Response 200 "<!doctype h...">
response.code # => 200

response.cookies: クッキー

response.cookies # => {"Foo"=>"BAR", "QUUX"=>"QUUUUX"}

response.headers: レスポンスヘッダ

response.headers # => {:content_type=>"image/jpg; charset=utf-8", :cache_control=>"private" ... }
response.headers[:content_type]  # => 'image/jpg'

resopnse.raw_headers: レスポンスヘッダ(raw)

response.body: レスポンスボディ

response.body # => "<!doctype html>\n<html>\n<head>\n    <title>Example Domain</title>\n\n ..."

response.to_s: レスポンスボディ(alias)

response.request: リクエストオブジェクト

  • RestClient::Requestオブジェクト
response.request.url # => "http://httpbin.org/get"

response.cookie_jar: ?

response.history: リダイレクト時のレスポンスの配列

RESTfulなリソース

  • RestClient::ResourceはRESTfulなリソースを表現できる
  • ネストされたリソースなどに繰り返しアクセスしたい場合に便利

GET

resource = RestClient::Resource.new('http://some/resource')
resource.get

ネストされたリソース

# それぞれがResourceオブジェクトで、getメソッド等を使える
site       = RestClient::Resource.new('http://example.com') # http://example.com
posts      = site['posts']                                  # http://example.com/posts
first_post = posts['1']                                     # http://example.com/posts/1
comments   = first_post['comments']                         # http://example.com/posts/1/comments

site.get       # GET http://example.com
posts.get      # GET http://example.com/posts
first_post.get # GET http://example.com/posts/1
comments.get   # GET http://example.com/posts/1/comments

第二引数はオプション

  • RestClient::Request.executeのオプションと同じっぽい
# API
RestClient::Resource.new(url, options={}, backwards_compatibility=nil, &block)

# リクエストヘッダ
resource = RestClient::Resource.new('http://some/resource', :headers => { :client_version => 1 })

# タイムアウト
resource = RestClient::Resource.new('http://slow', :read_timeout => 10)
resource = RestClient::Resource.new('http://behindfirewall', :open_timeout => 10)

# BASIC認証
resource = RestClient::Resource.new('http://protected/resource', {:user => 'user', :password => 'password'})

クエリパラメータ

GET

# GET "https://httpbin.org/get?foo=bar&baz=qux"
# クエリパラメータはheadersのparamsで指定する。ややこしい。 -> 将来変更の可能性あり
RestClient.get('https://httpbin.org/get', params: {foo: 'bar', baz: 'qux'})

GET(Railsスタイルの配列)

  • デフォルトはRailsのスタイル(?)と同じ
# GET "https://http-params.herokuapp.com/get?foo[]=1&foo[]=2&foo[]=3"
response = RestClient.get('https://http-params.herokuapp.com/get', params: {foo: [1,2,3]})

GET(フラットな配列)

  • フラットな配列が欲しい場合は、RestClient::ParamsArrayを使う
# GET "https://httpbin.org/get?foo=1&foo=2"
RestClient.get('https://httpbin.org/get', params: RestClient::ParamsArray.new([[:foo, 1], [:foo, 2]]))

GET(入れ子のハッシュ)

# GET "https://http-params.herokuapp.com/get?outer[foo]=123&outer[bar]=456"
response = RestClient.get('https://http-params.herokuapp.com/get', params: {outer: {foo: 123, bar: 456}})

POST

# POST "https://httpbin.org/post", data: "foo=bar&baz=qux"
RestClient.post('https://httpbin.org/post', {foo: 'bar', baz: 'qux'})

POST(JSON payload)

  • rest-clientはJSONをそのままでは扱えないので、自分でJSON文字列にする必要がある
payload = {'name' => 'newrepo', 'description': 'A new repo'}
RestClient.post('https://api.github.com/user/repos', payload.to_json, content_type: :json)

タイムアウト

timeout: open_timeoutとread_timeoutを両方同時にセット

  • デフォルトは60s
  • nilにするとタイムアウトしなくなる
  • open_timeoutとread_timeoutを使うとより細かく指定できる
RestClient::Request.execute(method: :get, url: 'http://example.com/resource',
                            timeout: 120)

open_timeout: コネクションを開くまでに待つ最大秒数

RestClient::Request.execute(method: :get, url: 'http://example.com/resource',
                            read_timeout: 120, open_timeout: 240)

read_timeout: データ読み込みまでに待つ最大秒数

リダイレクト

基本的な使い方

# ステータスコードが300台の場合は、自動でリダイレクトしてくれる
response = RestClient.get('http://httpbin.org/redirect/2')

# response.historyで、リダイレクトによる一連のレスポンスにアクセスできる
response.history # => [<RestClient::Response 302 "<!DOCTYPE H...">, <RestClient::Response 302 "">]

# `max_redirects: 0`で自動リダイレクトしないようにできる
RestClient::Request.execute(method: :get, url: 'http://httpbin.org/redirect/1', max_redirects: 0) # `RestClient::Found: 302 Found`例外が投げられる

# 手動リダイレクト
begin
  # 自動リダイレクトしないようにする
  RestClient::Request.execute(method: :get, url: 'http://httpbin.org/redirect/1', max_redirects: 0)
rescue RestClient::ExceptionWithResponse => err

  # 例外オブジェクト
  err # => #<RestClient::Found: 302 Found>

  # 例外を通してresponseにアクセスできる
  err.response # => RestClient::Response 302 "<!DOCTYPE H..."
  err.response.headers[:location] # => "/get"

  # 手動リダイレクト
  err.response.follow_redirection # => RestClient::Response 200 "{\n  "args":..."
end

POSTのリダイレクト

  • 自動リダイレクトはGET/HEADだけ。POSTとかは自分で.follow_redirectionを使って手動リダイレクトする
  • いくつか方法がある。例外スタイルがおすすめらしい

例外スタイルの場合

  • リダイレクト系の例外をrescueして、手動リダイレクト
begin
  RestClient.post('http://example.com/redirect', 'body')
rescue RestClient::MovedPermanently,
       RestClient::Found,
       RestClient::TemporaryRedirect => err

  err.response.follow_redirection # 例外(301, 302, 307)の場合は、手動リダイレクト
end

例外スタイル + ステータスコードで分岐の場合

  • 例外をまるごとrescueして、ステータスコードで分岐からの、手動リダイレクト
begin
  RestClient.post('http://example.com/redirect', 'body')
rescue RestClient::ExceptionWithResponse => err

  # 例外(301, 302, 307)の場合は、手動リダイレクト
  case err.http_code
  when 301, 302, 307
    err.response.follow_redirection
  else
    raise
  end

end

ブロックスタイルの場合

  • ブロックでresponse時の処理を指定できる。そこでステータスコードで分岐からの、手動リダイレクト
RestClient.post('http://example.com/redirect', 'body') { |response, request, result|
  case response.code
  when 301, 302, 307
    response.follow_redirection  # 手動リダイレクト
  else
    response.return!             # デフォルトの処理
  end
}

ファイル転送(ストリーミング)

リクエスト

# ファイルアップロード(text/plain)
RestClient.put('http://httpbin.org/put',
               File.open('/tmp/foo.txt', 'r'),
               content_type: 'text/plain')


# ファイルアップロード(multipart/form-data)
RestClient.put('http://httpbin.org/put',
               {file_a: File.open('a.txt', 'r'),
               file_b: File.open('b.txt', 'r')})

レスポンス

  • RestClient.getの時、レスポンスはメモリにバッファされる。しかしisoなどの大きいデータの場合は、メモリに収まらないのでファイルに直接ストリームしたい。 -> 2つの方法がある

raw_response: Tempfileに保存する

  • raw_response: trueにすると、レスポンスをTempfileに保存する
  • RestClient::Responseオブジェクトの代わりに、RestClient::RawResponseオブジェクトを返す
raw_response = RestClient::Request.execute(
                 method: :get,
                 url: 'http://releases.ubuntu.com/16.04.2/ubuntu-16.04.2-desktop-amd64.iso',
                 raw_response: true
               )
# => <RestClient::RawResponse @code=200, @file=#<Tempfile:/tmp/rest-client.20170522-5346-1pptjm1>, @request=<RestClient::Request @method="get", @url="http://releases.ubuntu.com/16.04.2/ubuntu-16.04.2-desktop-amd64.iso">>

raw_response.file.size # => 1554186240
raw_response.file.path # => "/tmp/rest-client.20170522-5346-1pptjm1"

block_response: procを使い自分でファイルに書き込む

File.open('/some/output/file', 'w') {|f|
  # proc
  # responseはNet::HTTPのNet::HTTPResponseオブジェクト。これを使い、自分でファイルに書き込む
  block = proc { |response|
    response.read_body do |chunk|
      f.write chunk
    end
  }

  # リクエスト
  RestClient::Request.execute(method: :get,
                              url: 'http://example.com/some/really/big/file.img',
                              block_response: block)
}

プロキシ

グローバル

# プロキシサーバを利用する
RestClient.proxy = "http://proxy.example.com/"
RestClient.get "http://some/resource"

# http_proxy環境変数を使う場合は、自分でしていしないといけないっぽい?
RestClient.proxy = ENV['http_proxy']

リクエスト毎

# このリクエストだけ、プロキシサーバを利用する
RestClient::Request.execute(method: :get, url: 'http://example.com',
                            proxy: 'http://proxy.example.com')

# このリクエストだけ、プロキシサーバを利用しない
RestClient.proxy = "http://proxy.example.com/"
RestClient::Request.execute(method: :get, url: 'http://example.com', proxy: nil)

クッキー

get

response = RestClient.get 'http://example.com/action_which_sets_session_id'
response.cookies # => {"_applicatioN_session_id" => "1234"}

set

  • 後方互換性のため、headersとして指定可能
  • クッキーとして使えるオブジェクトは3種類
    • Hash{String/Symbol => String}
    • ArrayHTTP::Cookie
    • HTTP::CookieJar(http-cookie gemの機能)
# hashとして指定
# Set-Cookie: foo=Value; Domain=.example.com; Path=/
# Set-Cookie: bar=123; Domain=.example.com; Path=/
RestClient::Request.new(
  url:     'http://example.com',
  method:  :get,
  cookies: {:foo => 'Value', 'bar' => '123'}
)

# HTTP::CookieJarとして指定
jar = HTTP::CookieJar.new
jar.add HTTP::Cookie.new('foo', 'Value', domain: 'example.com', path: '/', for_domain: false)
RestClient::Request.new(..., :cookies => jar)


# postで利用(headersとして指定可能)
response2 = RestClient.post(
  'http://localhost:3000/',
  {:param1 => "foo"},
  {:cookies => {:session_id => "1234"}}
)

SSL/TLS

# HTTPSでは、自動でシステムのCA証明書を使う
RestClient.get 'https://user:password@example.com/private/resource'

# クライアント証明書を指定
RestClient::Resource.new(
  'https://example.com',
  :ssl_client_cert  =>  OpenSSL::X509::Certificate.new(File.read("cert.pem")),
  :ssl_client_key   =>  OpenSSL::PKey::RSA.new(File.read("key.pem"), "passphrase, if any"),
  :ssl_ca_file      =>  "ca_certificate.pem",
  :verify_ssl       =>  OpenSSL::SSL::VERIFY_PEER
).get

# SSLのエラーが起きる場合はverifyを無視できる
# もちろんセキュリティー的に問題ありなので本番で使わないこと
RestClient.get 'https://user:password@example.com/private/resource', verify_ssl: false

ログ

グローバル

  • logの指定方法は4つ
    • loggerオブジェクト
    • ファイル名
    • 'stdout'
    • 'stderr'
RestClient.log = 'stdout'

# 環境変数でもOK
ENV['RESTCLIENT_LOG'] = 'stdout'

リクエスト単位

# logオプションを使う
resource = RestClient::Resource.new 'http://example.com/resource', log: Logger.new(STDOUT)
RestClient::Request.execute(method: :get, url: 'http://example.com/foo', log: Logger.new(STDERR))

レスポンスのコールバック

基本

  • ブロックを使うと、レスポンスのコールバックを指定できる
  • ブロックの戻り値が、戻り値になる
RestClient.get('http://example.com/nonexistent') # 例外を投げる
RestClient.get('http://example.com/nonexistent') {|response, request, result| response } # => <RestClient::Response 404 "<!doctype h...">

ステータスコードで処理を分岐

  • ブロックではデフォルトの処理をしなくなるので、デフォルトの処理をしてほしい場合はresponse.return!(&block)を行う
  • ブロックを使う方法よりも、rescueを使う方法のほうが自然でおすすめらしい
# ブロックを使う方法
RestClient.get('http://example.com/resource') { |response, request, result, &block|
  case response.code
  when 200; response                           # 200の場合は、レスポンスをそのまま返す
  when 423; raise SomeCustomExceptionIfYouWant # 423の場合は、指定の例外
  else    ; response.return!(&block)           # その他の場合は、デフォルトの処理
  end
}

# rescueを使う方法(こっちのが自然でおすすめらしい)
begin
  # リクエスト
  response = RestClient.get('http://example.com/resource')

rescue RestClient::Unauthorized, RestClient::Forbidden => err
  # エラー1の場合
  puts 'Access denied'
  return err.response

rescue RestClient::ImATeapot => err
  # エラー2の場合
  puts 'The server is a teapot! # RFC 2324'
  return err.response

else
  # 成功の場合
  puts 'It worked!'
  return response
end

リクエスト失敗例外

リクエスト失敗例外の継承階層

  • リクエスト時はこれらの例外をrescueする
  • ステータスコード単位なら、RestClient::NotFoundなどをrescueする。まとめてrescueしたいならRestClient::RequestFailedをrescueする
RuntimeError: Rubyの例外
RestClient::Exception: RestClientのベースとなる例外
RestClient::ExceptionWithResponse: リクエスト失敗時の例外(互換性のため存在)
RestClient::RequestFailed: リクエスト失敗時の例外
RestClient::NotFound等: ステータスコードに対応した例外(404なら、RestClient::NotFound)

リクエスト失敗例外をキャッチする

# キャッチしない
RestClient.get 'http://example.com/nonexistent' # => RestClient::NotFoundが投げられる

# キャッチする
begin
  RestClient.get 'http://example.com/nonexistent'
rescue RestClient::NotFound => exception
  exception.response # => <RestClient::Response 404 "<!doctype h...">
end

exception.response: レスポンス

exception.http_code: ステータスコード

exception.http_headers: レスポンスヘッダ

exception.http_body: レスポンスボディ

フック

  • RestClient.add_before_execution_procを使うと、executionメソッドの実行前フックを仕掛けることができる
  • :before_execution_procオプションで、リクエスト単位での実行も可能っぽい
RestClient.add_before_execution_proc do |request, params|
  # 任意の処理
end

# リクエスト前に任意の処理が実行される
RestClient.get 'http://example.com'

restclientコマンド

  • gem installすると、シェルで使えるrestclientというコマンドがついてくる。
  • そんなに便利ではなさそう。使わないかなー。

rest-clientをrequireした状態で、irbを起動

$ restclient
>> RestClient.get 'http://example.com'

リソース指定

$ restclient http://example.com
>> put '/resource', 'data'

認証

$ restclient https://example.com user pass
>> delete '/private/resource'

設定ファイルを使う

1.設定ファイルを作成

# ~/.restclient

sinatra:
  url: http://localhost:4567
rack:
  url: http://localhost:9292
private_site:
  url: http://example.com
  username: user
  password: pass

2.設定ファイルを読み込み

# $ restclient https://example.com user pass と同じ
$ restclient private_site

単発のリクエスト

$ restclient get http://example.com/resource > output_body
$ restclient put http://example.com/resource < input_body

落ち葉拾い

requireの方法は2つ

# 推奨
require 'rest-client'

# 非推奨(後方互換性のため残ってる)
require 'rest_client'

その他

  • APIはSinatraのDSLに影響を受けた
  • Ruby 2.0+
  • v2.0でAPIが結構変わったので注意

参考サイト

https://github.com/rest-client/rest-client