パルカワ2

最近はFlutterをやっています

Dartのhttp packageでは物足りない部分を補う

http packageを使っている。 pub.dev

まあ、便利なんだけどtimeoutとcookieが対応していない。またUserAgentは常に指定する必要がある。 なので、それらに対応するには別packageを使うか自分で頑張る他ない。

諸々対応しているHTTP Clientとしてdioがあるが、自前で頑張っている+中国語でドキュメントが書かれていたりするので、今回は自分で頑張ることにした。

import 'dart:io';

import 'package:cookie_jar/cookie_jar.dart';
import 'package:http/http.dart' as http;

class HttpClient extends http.BaseClient {
  static const userAgent =
      "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36";

  final http.Client _inner = http.Client();
  final CookieJar _cookieJar = CookieJar();
  final Duration _timeout = Duration(seconds: 10);

  @override
  Future<http.StreamedResponse> send(http.BaseRequest request) async {
    print("${request.method} ${request.url}");

    // set User-Agent
    request.headers[HttpHeaders.userAgentHeader] = userAgent;

    final cookies = _cookieJar.loadForRequest(request.url);
    _removeExpiredCookies(cookies);

    String cookie = _getCookies(cookies);
    if (cookie.isNotEmpty) {
      request.headers[HttpHeaders.cookieHeader] = cookie;
    }

    final response = await _inner.send(request).timeout(_timeout);

    if (response != null && response.headers != null) {
      final cookieHeader = response.headers[HttpHeaders.setCookieHeader];
      _saveCookies(response.request.url, cookieHeader);
    }

    return response;
  }

  void _removeExpiredCookies(List<Cookie> cookies) {
    cookies.removeWhere((cookie) {
      if (cookie.expires != null) {
        return cookie.expires.isBefore(DateTime.now());
      }
      return false;
    });
  }

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

  void _saveCookies(Uri uri, String cookieHeader) {
    if (cookieHeader == null || cookieHeader.isEmpty) {
      return;
    }
    // set-cookieが複数あった場合は、","でjoinして返ってくるので分割する必要がある
    final cookies = cookieHeader.split(",");
    if (cookies.isEmpty) {
      return;
    }
    _cookieJar.saveFromResponse(
      uri,
      cookies.map((cookie) => Cookie.fromSetCookieValue(cookie)).toList(),
    );
  }
}

という感じでやっている。 また何度もリクエストするようなコードを書いていると Connection closed before full header was received というエラーが出ることがあり、それは1秒ほどdelayすると起きない。

final response1 = await http.get(...);
...
await Future<void>.delayed(Duration(seconds: 1));
...
// delayをしないとエラーになるが、delayするとエラーが起きない
final response2 = await http.get(...);