LoginSignup
22
16

More than 3 years have passed since last update.

FlutterでCookieを利用する。

Last updated at Posted at 2020-09-08

はじめに

本記事では、以下の状況を想定しています。

  • FlutterのアプリケーションでAPIを飛ばす時にCookieを利用したい。
  • APIで保存したCookieをWebViewでも使いたい。

環境

  • Flutter 1.20.3
  • HTTPクライアント: dio 3.0.10
  • Cookie管理: CookieJar 1.0.1

APIを飛ばす時にCookieを利用したい

既にFlutter(Dart)では、Cookieの仕組みが提供されていますので、それを利用します。 import 'dart:io';

CookieJarを利用すると、URIをキーにCookieの値を管理してくれます。
List<Cookie> loadForRequest(Uri uri) で保存されたCookieを取得し、
void saveFromResponse(Uri uri, List<Cookie> cookies) で、UriをキーにCookieを保存します。

HTTPクライアントでの準備です。
重要な箇所はコメントにて説明します。

class RestClient {
  factory RestClient() {
    return _restClient ??= RestClient._internal();
  }
  RestClient._internal() {
    // interceptorsに、後述するIntercepterの実装クラスを渡すことで、
    // リクエスト送信時、レスポンス受信時に何か処理を行うことができる。
    _http.interceptors.add(CookieManager(_cookieJar));
  }
  static RestClient _restClient;
  final _http = Dio();
  final _cookieJar = CookieJar();
}

/// Interceptorを実装した独自クラスを定義する。
class CookieManager extends Interceptor {
  CookieManager(this.cookieJar);
  final CookieJar cookieJar;

  /// リクエスト送信時に実行したい処理を記載
  @override
  Future onRequest(RequestOptions options) async {
    final cookies = cookieJar.loadForRequest(options.uri)
      ..removeWhere((cookie) {
        if (cookie.expires != null) {
          // 期限切れなら削除する
          return cookie.expires.isBefore(DateTime.now());
        }
        return false;
      });
    // オブジェクトのcookieをリクエストヘッダ用に文字列に変換する
    final cookie = getCookies(cookies);
    if (cookie.isNotEmpty) {
      options.headers[HttpHeaders.cookieHeader] = cookie;
    }
  }

  /// レスポンス受信時に、Cookieを保存する処理を実行
  @override
  Future onResponse(Response response) async => _saveCookies(response);

  @override
  Future onError(DioError err) async => _saveCookies(err.response);

  void _saveCookies(Response response) {
    if (response == null || response.headers == null) {
      return;
    }
    // HttpHeadersに、レスポンスヘッダのCookieのKeyが定義されている。他にも様々なヘッダのKeyが記載されている。
    final cookies = response.headers[HttpHeaders.setCookieHeader];
    if (cookies == null) {
      return;
    }
    // cookieの保存処理を実行
    cookieJar.saveFromResponse(
      response.request.uri,
      cookies
          .map((str) {
            try {
              // 補足1参照
              final cookie = Cookie.fromSetCookieValue(str)
                ..domain = response.request.uri.host;
              return cookie;
            } on Exception catch (e) {
              logger.warning(e);
              return null;
            }
          })
          .where((element) => element != null)
          .toList(),
    );
  }

  static String getCookies(List<Cookie> cookies) {
    return cookies.map((cookie) => '${cookie.name}=${cookie.value}').join('; ');
  }
}

補足1

Dart標準に用意されているCookie.fromSetCookieValueは、Cookieの文字列をいい感じにCookieオブジェクトに変換してくれる便利なものです。
しかしながら、Cookieの文字列に []などの記号が含まれていると、FormatExceptionをthrowするため、Cookieが保存されず、Cookieオブジェクトに変換されません。

対処方法は2つ考えられます。
1. 投げられる例外を握り潰す。(上記例で記載されている方法)

ひとまず正常なCookieだけは生成されます。

  1. Cookieクラスを実装し直す。(参考: https://juejin.im/post/6844903934042046472)

例外が投げられる原因は、要するに、

Dartは RFC2616 に準拠した実装になってはいるが、必ずしもサーバ側はそうではないから。だそうです。

暫定対処として、Cookie.fromSetCookieValueの定義元を辿ると、ソースコードが見れますので、それをコピペして、必要な処理を書き換える方法になります。

class MyCookie implements Cookie {
  MyCookie(String name, String value)
      : _name = _validateName(name),
        _value = _validateValue(value),
        httpOnly = true;
  MyCookie.fromSetCookieValue(String value)
      : _name = '',
        _value = '' {
    // Parse the 'set-cookie' header value.
    _parseSetCookieValue(value);
  }

  // _parseSetCookieValueを実装する
}

改善のためのIssueは挙げられています。
https://github.com/dart-lang/sdk/issues/42902

APIで保存したCookieをWebViewでも使いたい。

webview_flutter を使用する場合、合わせて以下のパッケージも使用する必要があります。
webview_cookie_manager

※これがマージされれば、webview_cookie_managerは必要なくなるかもしれません。
https://github.com/flutter/plugins/pull/2999

// ...
final _cookieManager = WebviewCookieManager();
// ...
WebView(
  initialUrl: url,
  javascriptMode: JavascriptMode.unrestricted,
  onWebViewCreated: (controller) async {
    // cookies: List<Cookie>

    // ClearしないとCookieが反映されない。
    await _cookieManager.clearCookies();
    if (cookies != null) {
      await _cookieManager.setCookies(cookies);
    }
   // ...
  },
)

Cookieがうまく読み込まれない場合は、initialUrlを指定するのではなく、controller.loadUrlを使用するといいかもしれません。

// ...
final _cookieManager = WebviewCookieManager();
// ...
WebView(
  javascriptMode: JavascriptMode.unrestricted,
  onWebViewCreated: (controller) async {
    // cookies: List<Cookie>

    // ClearしないとCookieが反映されない。
    await _cookieManager.clearCookies();
    if (cookies != null) {
      await _cookieManager.setCookies(cookies);
    }
    // Cookieを設定した後に、URLを読み込む
    await controller.loadUrl(url);
   // ...
  },
)

flutter_webview_plugin で使用する場合は、文字列にしてheadersに指定するだけです。

// cookies: List<Cookie>
WebviewScaffold(
  withLocalStorage: true,
  withJavascript: true,
  headers: {if (cookies != null) 'Cookie': cookies.map((cookie) => '${cookie.name}=${cookie.value}').join('; ');},
  // ...
)
22
16
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
22
16