Dartでhttpライブラリを使用した際の複数set-cookieの対応方法
http ライブラリから返却された複数の set-cookie
始めに
どうも、真也です。
今回は Dart言語
で HTTP通信
[1] をする際のデファクトスタンダードである http
[2] ライブラリが抱える Cookie に関する問題と暫定的な解決策について記事を書いていきます。
まず http ライブラリとはなにか
この http
ライブラリはとても洗練されていて完成度が高く、非常に簡単に HTTP通信
を実装することができます。
例えば、Dart言語
で GET通信
をしたければ http
を使用して以下の処理を記述するだけで動作します。
import 'package:http/http.dart' as http;
void main() async {
final response = await http.get(Uri.parse('https://example.com'));
}
POST通信
をしたければ以下のような感じです。
import 'package:http/http.dart' as http;
void main() async {
final response = await http.post(Uri.parse('https://example.com'), body: {
'name': 'doodle',
'color': 'blue',
});
}
簡潔で素晴らしいですね。
なにが問題なのか?
しかし、とても残念な仕様もいくつかあります。その残念な仕様の一つが今回の記事の主題となる Cookie の扱われ方 です。
http
ライブラリを使用して HTTP通信
をした際に、レスポンスヘッダーに set-cookie
が一つだけ設定されて返却される場合は問題ないのですが、複数の set-cookie
が設定される場合にはカンマ区切りで結合されて返却されます。
具体的には以下のような場合です。
問題となる場面
例えば、以下のような複数の set-cookie
が返却されてくる場面が問題になります。
set-cookie: AWSALB=TEST; Expires=Tue, 03 May 2022 02:03:35 GMT; Path=/
set-cookie: AWSALBCORS=TEST; set-cookie: Expires=Tue, 03 May 2022 02:03:35 GMT; Path=/; SameSite=None; Secure
set-cookie: jwt_token=TEST; Domain=.example.com; Max-Age=31536000; Path=/; expires=Wed, 26-Apr-2023 02:03:35 GMT; SameSite=lax; Secure
set-cookie: csrf_token=TEST; Domain=.example.com; Max-Age=31536000; Path=/; expires=Wed, 26-Apr-2023 02:03:35 GMT
set-cookie: csrf_token=TEST; Domain=.example.com; Max-Age=31536000; Path=/; expires=Wed, 26-Apr-2023 02:03:35 GMT
これら複数の set-cookie
を http
ライブラリから返却された headers
フィールドから使用しようとするとこのようになります。
import 'package:http/http.dart' as http;
void main() async {
final response = await http.get(Uri.parse('https://example.com'));
print(response.headers['set-cookie']);
}
AWSALB=TEST; Expires=Tue, 03 May 2022 02:03:35 GMT; Path=/,AWSALBCORS=TEST; set-cookie: Expires=Tue, 03 May 2022 02:03:35 GMT; Path=/; SameSite=None; Secure,jwt_token=TEST; Domain=.example.com; Max-Age=31536000; Path=/; expires=Wed, 26-Apr-2023 02:03:35 GMT; SameSite=lax; Secure,csrf_token=TEST; Domain=.example.com; Max-Age=31536000; Path=/; expires=Wed, 26-Apr-2023 02:03:35 GMT,csrf_token=TEST; Domain=.example.com; Max-Age=31536000; Path=/; expires=Wed, 26-Apr-2023 02:03:35 GMT,
もともと別々の set-cookie
で設定されていた Cookie
がカンマ区切りで結合されているのがわかりますね。このままの形式では使い物にならないのでどうにかして変換する必要があります。
暫定的な解決策
問題となっているカンマ区切りで結合された set-cookie
を変換するのは一筋縄ではいきません。「カンマ区切りで結合されているのであればカンマで分割すればいいではないか」と考える方もいるかもしれませんが、Cookie
の各フィールドにカンマが含まれている場合があるので有効な手段ではありません。
そこで、暫定的な解決策として現状としては以下の 2 つの選択肢があります。
- 正規表現で分割する
- 私が作ったライブラリを使用する
いずれも根本的な解決ではないのですが、動作確認ができている暫定的な解決策として順に紹介していきます。
解決策 1: 正規表現で分割
開発しているアプリやパッケージに依存性を増やしたくないという方にとっては正規表現で分割するのが最適な手段になるでしょう。
具体的には以下の実装で問題となっている結合された set-cookie
を分割して使用できます。
import 'dart:io';
import 'package:http/http.dart' as http;
/// 結合されたset-cookieを分割する正規表現
final _regexSplitSetCookies = RegExp(',(?=[^ ])');
void main() async {
final response = await http.get(Uri.parse('https://example.com',));
final List<Cookie> cookies = [];
final setCookie = _getSetCookie(response.headers);
if (setCookie.isNotEmpty) {
for (final cookie in setCookie.split(_regexSplitSetCookies)) {
cookies.add(Cookie.fromSetCookieValue(cookie));
}
}
// 結合されたset-cookieが個別のset-cookieに分割されているのを確認できます。
print(cookies);
}
String _getSetCookie(final Map<String, dynamic> headers) {
for (final key in headers.keys) {
// システムによって返却される "set-cookie" のケースはバラバラです。
if (key.toLowerCase() == 'set-cookie') {
return headers[key] as String;
}
}
return '';
}
解決策 2: ライブラリを使用
依存ライブラリを増やすことも選択肢の内に入る方は私が過去に作成したライブラリを使用できます。
ライブラリの中身で行われる処理は先に紹介した正規表現で分割する方法とまったく同じになります。
例えば、以下のように使用できます。
import 'package:http/http.dart' as http;
import 'package:sweet_cookie_jar/sweet_cookie_jar.dart';
void main() async {
final response = await http.get(Uri.parse('https://example.com'));
// やることはResponseオブジェクトを渡すだけです。
final cookieJar = SweetCookieJar.from(response: response);
print(cookieJar.find(name: 'csrf_token'));
}
Dart 開発チームにプルリクエストを送りました
この問題を解決するための最も良い方法は http
ライブラリ自体の処理を修正してこの困難さを解決することです。
ここまでいくつかの暫定的な解決策を並べてきましたが、ライブラリの不出来な部分を個々人の実装で補うというのはスマートではありません。長く愛され使われるライブラリだからこそ、より開発者が扱いやすい機能を標準で提供すべきです。
そのため、私は先に紹介した SweetCookieJar
の一部のアルゴリズムを http
ライブラリに移植するプルリクエストを送り、現在 Dart開発チーム
とマージに向けた調整と協議をしています。
Discussion