[Rails] RESTfulAPIをORMするgem “her” の利用方法(1)
モバイルアプリサービス部@モバイルバックエンドグループの五十嵐です。
これから何回かに分けてherの使い方を書いていこうと思います。
概要
herはActiveRecordのように振る舞うRESTfulAPIのORMです。
今回は、herを使ってOAuth2.0のClientCredentialsGrantのアクセストークンを利用するAPIにアクセスしてみます。
環境
- Ruby: 2.2.4p230
- Rails: 4.2.5.1
- Gems:
- her (0.8.1)
- webmock (1.22.6)
- rspec-rails (3.4.2)
Step.1 アクセスの都度トークンを取得する
まずは、APIにアクセスする都度アクセストークンをリクエストして取得し、取得したアクセストークンをAPIのリクエストヘッダーにセットするようにします。
アクセストークンのモデルを作成する
ClientCredentialsGrantでアクセストークンを取得するAPIを実行するモデルを作成します。FaradayでOauthサーバのアクセストークン発行エンドポイントにBasic認証付きでPOSTリクエストするようにします。
# app/models/access_token.rb require 'base64' class AccessToken def self.get client = Faraday.new 'OauthサーバのURI' do |b| b.adapter Faraday.default_adapter b.authorization :Basic, Base64.encode64('client_id:client_secret') b.response :json end response = client.post 'アクセストークン発行するエンドポイント' response.body['access_token'] end end AccessToken.get # => 'access_token'
アクセストークンを取得するfaradayのミドルウェアを作成する
APIリクエストのHTTPヘッダーに Authorization: Bearer access_token
を付与するミドルウェアを作成します。 lib/
ディレクトリ内はデフォルトではautoloadされないので、autoloadパスに追加しましょう。
# lib/faraday/client_credential.rb class ClientCredential < Faraday::Middleware def call(env) env[:request_headers]['Authorization'] = "Bearer #{AccessToken.get}" @app.call(env) end end
アクセストークンを取得するのミドルウェアを追加する
作成したミドルウェアを Her::API
にデフォルトの設定として設定することで、herを使ってAPIアクセスする度にアクセストークンを取得し、リクエストヘッダーにアクセストークンを付与することができます。
# config/initializers/her.rb Her::API.setup url: Settings.custom.server do |c| # Request c.use ClientCredential ... # Response ... # Adapter ... end
確認
ここまでできたら、リクエストログなどからリクエストヘッダーにアクセストークンがセットされていることを確認してみましょう。
Step.2 トークンの有効期限が切れたらトークンを再取得する
トークンリクエストの都度、新しいアクセストークンが発行される場合はStep.1まで問題ありませんが、されない場合はAPIのリクエスト時にアクセストークンが期限切れになる可能性があります。Step.2では、アクセストークンの有効期限が切れた場合(401エラーが返された場合)、再度アクセストークンを取得し、APIリクエストをリトライする機能をfaradayのミドルウェアで実装してみます。
リトライするミドルウェアを設定する
素晴らしいことにfaradayにはリトライするミドルウェアがデフォルトで(しかも高機能で)ありますので、そのまま利用します。
exceptions
オプションにはリトライをする条件となるExceptionのクラスを指定します。デフォルトでは401エラーのExceptionが定義されていないので、CustomRaiseError::UnauthorizedError
はこのあとで作成します。
# config/initializers/her.rb Her::API.setup url: Settings.custom.server do |c| # Request c.use Faraday::Request::Retry, max: 1, interval: 0.05, interval_randomness: 0.5, backoff_factor: 2, exceptions: [CustomRaiseError::UnauthorizedError] c.use ClientCredential ... # Response ... # Adapter ... end
注意: faradayのミドルウェアは上から順に実行されるので、この順番は守ってください。Faraday::Request::Retry
ミドルウェアはリクエスト情報を記録するため、もし ClientCredential
ミドルウェアを上に書いてしまうと、トークン情報が記録され、リトライ時にトークンの再取得が行われなくなってしまいます。
カスタムエラーを起こすミドルウェアを定義する
faradayにはリクエストエラー時にエラーを発生させるミドルウェアがありますが、401エラーの実装がないため、既存の Faraday::Response::RaiseError
ミドルウェアをオーバーライドして、401エラーが発生した時の挙動を追加します。また、401エラーを示す UnauthorizedError
も定義します。
# lib/faraday/custom_raise_error.rb class CustomRaiseError < Faraday::Response::RaiseError # オーバーライド def on_complete(env) case env[:status] # 以下の2行を追加 when 401 raise UnauthorizedError, response_values(env) when 404 raise Faraday::Error::ResourceNotFound, response_values(env) when 407 raise Faraday::Error::ConnectionFailed, %{407 "Proxy Authentication Required "} when ClientErrorStatuses raise Faraday::Error::ClientError, response_values(env) end end # 401エラーのExceptionを追加 class UnauthorizedError < Faraday::ClientError; end end
カスタムエラーを起こすミドルウェアを追加する
作成した CustomRaiseError
ミドルウェアの設定を追加します。
# config/initializers/her.rb Her::API.setup url: Settings.custom.server do |c| # Request c.use Faraday::Request::Retry, max: 1, interval: 0.05, interval_randomness: 0.5, backoff_factor: 2, exceptions: [CustomRaiseError::UnauthorizedError] c.use ClientCredential ... # Response c.use CustomRaiseError ... # Adapter ... end
確認
リトライの挙動は目視では確認しにくいのでspecを書きました。
- リトライ回数の設定は1なので、トークンリクエストとAPIリクエストそれぞれが合計2回のリクエストが実行されること
- 3回目のリクエストエラーでは
CustomRaiseError::UnauthorizedError
エラーが発生すること
# spec/models/resource_retry_spec.rb require 'rails_helper' require 'webmock/rspec' RSpec.describe Resource, type: :model do describe '401エラーが応答された時' do before do @access_token_stub = WebMock.stub_request(:post, 'アクセストークン発行するエンドポイント'). to_return(status: 200, body: %Q!{ "access_token": "access_token" }!) end before do @resource_stub = WebMock.stub_request(:get, 'リソースのエンドポイント').to_return(status: 401) end it 'アクセストークンリクエストが2回発生すること' do expect{ Resource.find(1) }.to raise_exception(CustomRaiseError::UnauthorizedError) expect(@access_token_stub).to have_been_made.times(2) end it 'APIリクエストが2回発生すること' do expect{ Resource.find(1) }.to raise_exception(CustomRaiseError::UnauthorizedError) expect(@resource_stub).to have_been_made.times(2) end end end
まとめ
今回紹介した内容の大部分は、herの機能というよりherが使うHTTPクライアントのfaradayの機能でしたが、faradayのミドルウェアを理解することでこの先、よりherを使いこなすことができるようになりますので、ぜひ習得しましょう。