この記事は公開されてから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を使いこなすことができるようになりますので、ぜひ習得しましょう。