LoginSignup
0
0

Faradayで302でログイン画面に飛ばされたときに再ログイン

Last updated at Posted at 2024-01-28

RubyでHTTP通信して何か情報を取ってくるときには、だいたい Faradayを使うと楽だ。

あたりを見れば大体の使い方はわかる。かりに、使い方が分からなくてもソースコードを読めばなんとなく動きはわかる。

まえおき

CakePHPで作られた古い社内サービスをスクレイピングするスクリプトを組んでたんだけど、何か知らないけど、ときどきUser/Passを入れ直せと、ログイン画面に飛ばされてしまってた。

  • Authorizationヘッダーじゃなくて、クッキーで認証管理されてる
  • 再認証のときはHTTP 401ではなく302でログイン画面に飛ばされるだけ

ということで、Faradayの標準の仕組みではどうにもならなさそう、ということで調べた。

faraday-retryは??

Faradayには、通信エラーのときにリトライするのをうまくやる仕組みがある。

ただ、このfaraday-retryは、基本的にはFaradayの例外をキャッチして、それに対してリトライをかける仕組みだ。302はFaradayの例外ではないので、標準ではリトライがかからない。
無理やり以下のようにすればリトライすることはできるが、ユーザID/パスワードで認証するロジックがあっちにもこっちも状態(初回の認証時と、再認証時で異なる場所に実装される状態)になるので、なんだか微妙だ。もっといい方法はないだろうか?

retry_options = {
  retry_statuses: [302],
  methods: [],
  retry_if: ->(env, _exception) {
    # 302でログインページに飛ばされていたらtrueを返す
  },
  retry_block: ->(env) {
    # 再ログイン処理
  },
}

自前でFaradayミドルウェアを書いてみる

Faradayは、Rackを真似たようなミドルウェアの仕組みがある。
https://lostisland.github.io/faraday/#/middleware/index?id=how-it-works

class HogeMiddleware < Faraday::Middleware
  def initialize(app, options={})
    @app = app
  end

  def call(env)
    # 通信前に何かする
    response = @app.call(env)
    # 通信後に何かする

    response
  end
end

今回のようなケースでは、認証機能、再認証機能を全部おまかせするようなミドルウェアを書いてしまえばいい。

class MyAuthMiddleware < Faraday::Middleware
  def initialize(app, options={})
    @app = app
    @options = options
    fetch_token!
  end

  LOGIN_URL = 'https://xxx.example.com/login'.freeze

  def call(env)
    env.request_headers['cookies'] = "hogehoge_session=#{@token}"
    response = @app.call(env)
    if response.status == 302 && response.headers['location'] == LOGIN_URL
        # 認証トークンを取得し直して1回だけリトライ
      fetch_token!
      env.request_headers['cookies'] = "hogehoge_session=#{@token}"
      response = @app.call(env)
    end
    response
  end

  private

  def fetch_token!
    @token = LoginProcess.new(@options[:user], @options[:password]).call
  end
end

Faraday::Request.register_middleware(myauth: MyAuthMiddleware)

そうすれば、スクレイピング部分からは認証ロジックをほぼ追い出すことができて、

@connection = Faraday.new do |faraday|
  faraday.adapter Faraday.default_adapter
  faraday.request :myauth, user: ENV['MY_USERNAME'], password: ENV['MY_PASSWORD']
  ...
end

こんなかんじでメインの通信をおこなうFaradayコネクションを定義すればおk。

まとめ

通信エラーのリトライくらいならfaraday-retryで十分だが、認証・再認証あたりをする場合には、むりにfaraday-retryで頑張らず、本記事で紹介したように自前でミドルウェアを書いてみるとよい。
そんなに大変ではないし、それなりに役立つと思う。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0