7839

雑草魂エンジニアブログ

【Rails】Faraday2.0を使う

Railsアプリケーションで外部APIを使う場合に、導入していた Faraday。v1からv2にアップデートしており、保守性を考慮して少し仕様が変わっていた。Faradayの使い方を備忘録として残しておく。

Faradayとは

Faraday is an HTTP client library that provides a common interface over many adapters (such as Net::HTTP) and embraces the concept of Rack middleware when processing the request/response cycle.

  • HTTP クライアントライブラリである
  • Net::HTTP やその他の多くのアダプターに対して共通のインターフェースを提供する
    • Faraday 2.0より、net_httpのみを標準サポート
    • それ以外のアダプタはGemfileの追加が必要
  • Rack のようにミドルウェアの設定が可能

利用シーンとしては、Railsアプリケーションで外部のAPIなどにアクセスする場合などに用いられる。

v1からv2への更新内容は以下に記載されている。

インストール

gem 'faraday'
$ bundle install

faraday_middlewareもv1では利用していたが、v2では非推奨になった。
net_httpを使う場合、faradayのみで十分になっているように思えた。

Faraday Connection

外部のAPIを使う場合、 Faraday::Connection オブジェクトを生成することが推奨されている。 理由としては以下が挙げられる。

  • 共通のリクエストヘッダーの設定が可能
    • 'Content-Type' = 'application/json'の設定
    • authorizationの設定
  • APIのベースURLの設定が可能
  • アダプタやミドルウェアの設定が可能
    • loggerの設定

APIに関して基本的にJSONファイルでやり取りし、Bearer認証の場合、以下のミドルウェアの設定になる。

conn = Faraday.new('https://base-url.com/api/v1') do |conn|
  conn.request :json
  conn.response :json, parser_options: { symbolize_names: true }
  conn.authorization :Bearer, 'authentication-token'
  conn.request :instrumentation
end

JSON Request Middleware

conn = Faraday.new(...) do |conn|
  conn.request :json
end
  • Faraday::Request#bodyハッシュ(Key/Valueペア)をJSONリクエストボディに変換する
  • Content-Typeヘッダを自動的にapplication/jsonに設定する

JSON Response Middleware

conn = Faraday.new(...) do |conn|
  conn.response :json, **options
end
  • JSONのレスポンスボディをKey/Valueペアのハッシュにパースする
    • JSON.parseのoptionsを設定可能
    • ハッシュのキーを文字列ではなくシンボルにしたい場合
      conn.response :json, parser_options: { symbolize_names: true }
    

Authentication Middleware

Bearer認証

conn = Faraday.new(...) do |conn|
  conn.request :authorization, 'Bearer', 'authentication-token'
end

Basic認証

conn = Faraday.new(...) do |conn|
  conn.request :authorization, :basic, 'username', 'password'
end

Basicのユーザー名とパスワードを自動的にBase64エンコードしてくれる。

Instrumentation Middleware

ログに関して、Logger Middlewareで標準出力にログを出力することができる。しかしながら、ログの整形が好みでなかったので、Instrumentation Middlewareを使うようにした。

ActiveSupport::Notifications.instrumentActiveSupportが提供するInstrumentation API)を利用してイベントを発行している。

conn = Faraday.new(...) do |conn|
  conn.request :instrumentation, name: 'custom_name', instrumenter: MyInstrumenter
  ...
end

そのイベントをActiveSupport::Notifications.subscribeを使って、サブスクライブすることでデータを取得することができ、ログを好きなように整形して吐き出すことができる。

ActiveSupport::Notifications.subscribe('request.faraday') do |name, starts, ends, _, env|
  url = env[:url]
  http_method = env[:method].to_s.upcase
  request_body = env[:response].env.request_body
  duration = ends - starts
  response_body = env[:response].body
  status_logger = ActiveSupport::Logger.new('log/api-connection.log', 'daily')
  status_logger.formatter = proc do |severity, time, progname, msg|
    "[%s#%d] %5s -- %s: %s\n" % [format_datetime(time), $$, severity, progname, msg.force_encoding("UTF-8")]
  end
  status_logger.level = Rails.logger.level
  status_logger.info "#{env[:status]} #{http_method} '#{url}' #{request_body} #{duration}seconds data: #{response_body}"
end

日本語文字化け対応で、force_encoding("UTF-8")追加している。

GET,POST,PUT,PATCH,DELETE

  • get(url, params = nil, headers = nil)
  • post(url, body = nil, headers = nil)
  • put(url, body = nil, headers = nil)
  • patch(url, body = nil, headers = nil)
  • delete(url, params = nil, headers = nil)
response = connection.get('users', {
  page: 1,
  perPage: 10  
})
# => GET https://base-url.com/api/v1/users?page=1&perPage=10

response = connection.post('item', {
  name: 'hoge',
  price: 100
})
# => POST https://base-url.com/api/v1/item

if response.success?
  response.body[:id]
end

注意事項

  • urlの先頭にはスラッシュを入れない

POST 'application/x-www-form-urlencoded'

上記で作成したFaraday ConnectionはJSON形式なので、application/x-www-form-urlencodedにする場合は、以下のようにconn.request :url_encodedを指定する。

connection = Faraday.new('https://base-url.com/api/v1') do |conn|
  conn.request :url_encoded
  conn.response :json, parser_options: { symbolize_names: true }
  conn.authorization :Bearer, 'authentication-token'
  conn.request :instrumentation
end
response = connection.post('item', {
  name: 'hoge',
  price: 100
})

Tokenを上書きする場合

Tokenを上書きする場合は、以下のように直接上書きすればいい。

connection.headers['Authorization'] = "Bearer #{token}"

まとめ

改めて、FaradayのDocumentを読んでみて、とても便利だと思えた。Raise Error Middlewareも確かに便利であるが、モジュールの中ではなく、begin ~ rescue ~ endraiseで処理を明示的に書くようにした。