如何在Flutter中使用cookies进行HTTP请求?

46

我想向远程服务器发起HTTP请求,同时正确处理cookie(例如存储服务器发送的cookie,并在后续请求时发送这些cookie)。保留任何和所有cookie将是很好的。

我正在使用HTTP请求

static Future<Map> postData(Map data) async {
  http.Response res = await http.post(url, body: data); // post api call
  Map data = JSON.decode(res.body);
  return data;
}

要解析服务器发送的Cookie,您可以使用此软件包:https://pub.dev/packages/sweet_cookie_jar - user3563059
9个回答

78
这是一个如何获取会话 cookie 并在后续请求中返回它的示例。您可以轻松地将其调整为返回多个 cookie。创建一个名为 Session 的类,并通过它路由所有的 GETPOST 请求。
import 'package:http/http.dart' as http;

    class Session {
      Map<String, String> headers = {};
    
      Future<Map> get(String url) async {
        http.Response response = await http.get(url, headers: headers);
        updateCookie(response);
        return json.decode(response.body);
      }
    
      Future<Map> post(String url, dynamic data) async {
        http.Response response = await http.post(url, body: data, headers: headers);
        updateCookie(response);
        return json.decode(response.body);
      }
    
      void updateCookie(http.Response response) {
        String? rawCookie = response.headers['set-cookie'];
        if (rawCookie != null) {
          int index = rawCookie.indexOf(';');
          headers['cookie'] =
              (index == -1) ? rawCookie : rawCookie.substring(0, index);
        }
      }
    }

只是一个问题,你会在小部件中如何称呼它。 - ilovejavaAJ
2
根据RFC规定,所有标头都不区分大小写,尽管许多服务器不符合规范。为了方便比较,Dart强制将它们全部转换为小写。 - Richard Heap
那个结尾处的“index”位是干嘛用的?我不能只写成 headers['cookie'] = response.headers['set-cookie'] 吗? - daraul
1
@daraul,这里是要截去分号后面的部分(如果有的话)——请参见这里的讨论:https://dev59.com/hqvka4cB1Zd3GeqPrFD1#50299669 - Richard Heap
嘿,@RichardHeap,你能否请看一下这个问题:https://dev59.com/yXcPtIcB2Jgan1znP93p - Sandeep Sharma
显示剩余4条评论

22
我改进了Richard Heap的解决方案,使其能够处理多个“Set-Cookie”和多个cookie。
在我的情况下,服务器返回多个“Set-Cookie”。http包将所有Set-Cookie头连接在一个头中,并用逗号(',')分隔。
class NetworkService {

  final JsonDecoder _decoder = new JsonDecoder();
  final JsonEncoder _encoder = new JsonEncoder();

  Map<String, String> headers = {"content-type": "text/json"};
  Map<String, String> cookies = {};

  void _updateCookie(http.Response response) {
    String allSetCookie = response.headers['set-cookie'];

    if (allSetCookie != null) {

      var setCookies = allSetCookie.split(',');

      for (var setCookie in setCookies) {
        var cookies = setCookie.split(';');

        for (var cookie in cookies) {
          _setCookie(cookie);
        }
      }

      headers['cookie'] = _generateCookieHeader();
    }
  }

  void _setCookie(String rawCookie) {
    if (rawCookie.length > 0) {
      var keyValue = rawCookie.split('=');
      if (keyValue.length == 2) {
        var key = keyValue[0].trim();
        var value = keyValue[1];

        // ignore keys that aren't cookies
        if (key == 'path' || key == 'expires')
          return;

        this.cookies[key] = value;
      }
    }
  }

  String _generateCookieHeader() {
    String cookie = "";

    for (var key in cookies.keys) {
      if (cookie.length > 0)
        cookie += ";";
      cookie += key + "=" + cookies[key];
    }

    return cookie;
  }

  Future<dynamic> get(String url) {
    return http.get(url, headers: headers).then((http.Response response) {
      final String res = response.body;
      final int statusCode = response.statusCode;

      _updateCookie(response);

      if (statusCode < 200 || statusCode > 400 || json == null) {
        throw new Exception("Error while fetching data");
      }
      return _decoder.convert(res);
    });
  }

  Future<dynamic> post(String url, {body, encoding}) {
    return http
        .post(url, body: _encoder.convert(body), headers: headers, encoding: encoding)
        .then((http.Response response) {
      final String res = response.body;
      final int statusCode = response.statusCode;

      _updateCookie(response);

      if (statusCode < 200 || statusCode > 400 || json == null) {
        throw new Exception("Error while fetching data");
      }
      return _decoder.convert(res);
    });
  }
}

谢谢,你的回答帮助我检索多个cookie。 - Manish Dhruw
2
谢谢,这对我很有帮助。当您在“=”上拆分rawCookie时,存在一个小错误;当拆分中存在空字符串时,它无法正常工作。以下修复方法对我有用。rawCookie.split('=').where((s) => s.isNotEmpty).toList(growable: false) - Piyush Jajoo
2
同时在比较路径和过期时间时忽略大小写 - if (key.toLowerCase() != 'path' && key.toLowerCase() != 'expires') { return {key: value}; } - Piyush Jajoo
答案没有考虑到cookie值部分存在'='的情况。我需要改变_setCookie方法: 如果(rawCookie.length > 0){ int idx = rawCookie.indexOf("="); 如果(idx >= 0) { var key = rawCookie.substring(0, idx).trim(); var value = rawCookie.substring(idx+1).trim(); 如果(key == 'path' || key == 'expires' || key == 'domain' || key == 'SameSite') 返回; cookies[key] = value; } } - georkings

17

我发布了一个名为 requests 的小型flutter库,以帮助处理具有cookie感知能力的http请求。

dependencies:
  requests: ^3.0.1

使用方法:

import 'package:requests/requests.dart';

// ...

// this will persist cookies
var r1 = await Requests.post("https://example.com/api/v1/login", json: {"username":"...", "password":"..."} ); 
r1.raiseForStatus();

// this will re-use the persisted cookies
var r2 = await Requests.get("https://example.com/api/v1/stuff"); 
r2.raiseForStatus();
print(r2.json()['id'])

了解有关requests的更多信息


嗨,我正在使用graphql服务器和flutter_graphql包,它来自于这里https://github.com/zino-app/graphql-flutter。你有什么想法,我该如何使用这个包? - Nux
1
@Jossef 你好,有没有办法在不终止第一个请求的情况下发送第二个请求?我遇到了处理过期时间为“会话”的 cookie 的问题。 - CharukaHS
@CharukaHS 请打开一个问题 -> https://github.com/jossef/requests/issues 添加您的请求响应详细示例 - Jossef Harush Kadouri
似乎有旧的依赖关系,无法使用。 - RanH
@JossefHarushKadouri 这会将 cookie 存储在本地存储器中吗?假设我运行了此处提到的 post 和 get 请求,并关闭了应用程序,然后仅运行 get 请求(而不进行 post 请求),Requests.get 能否使用先前的 cookie?如果不能,那么该怎么办才能在本地保存 cookie 呢? - saket
显示剩余2条评论

5

我将larly的回答迁移到了nullsafety平台,并添加了'put'函数。

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


class NetworkService {
  final JsonDecoder _decoder = const JsonDecoder();
  final JsonEncoder _encoder = const JsonEncoder();

  Map<String, String> headers = {"content-type": "application/json"};
  Map<String, String> cookies = {};

  void _updateCookie(http.Response response) {
    String? allSetCookie = response.headers['set-cookie'];

    if (allSetCookie != null) {
      var setCookies = allSetCookie.split(',');

      for (var setCookie in setCookies) {
        var cookies = setCookie.split(';');

        for (var cookie in cookies) {
          _setCookie(cookie);
        }
      }

      headers['cookie'] = _generateCookieHeader();
    }
  }

  void _setCookie(String? rawCookie) {
    if (rawCookie != null) {
      var keyValue = rawCookie.split('=');
      if (keyValue.length == 2) {
        var key = keyValue[0].trim();
        var value = keyValue[1];

        // ignore keys that aren't cookies
        if (key == 'path' || key == 'expires') return;

        cookies[key] = value;
      }
    }
  }

  String _generateCookieHeader() {
    String cookie = "";

    for (var key in cookies.keys) {
      if (cookie.isNotEmpty) cookie += ";";
      cookie += key + "=" + cookies[key]!;
    }

    return cookie;
  }

  Future<dynamic> get(String url) {
    return http.get(Uri.parse(url), headers: headers).then((http.Response response) {
      final String res = response.body;
      final int statusCode = response.statusCode;

      _updateCookie(response);

      if (statusCode < 200 || statusCode > 400) {
        throw Exception("Error while fetching data");
      }
      return _decoder.convert(res);
    });
  }

  Future<dynamic> post(String url, {body, encoding}) {
    return http.post(Uri.parse(url), body: _encoder.convert(body), headers: headers, encoding: encoding).then((http.Response response) {
      final String res = response.body;
      final int statusCode = response.statusCode;

      _updateCookie(response);

      if (statusCode < 200 || statusCode > 400) {
        throw Exception("Error while fetching data");
      }
      return _decoder.convert(res);
    });
  }

  Future<dynamic> put(String url, {body, encoding}) {
    return http.put(Uri.parse(url), body: _encoder.convert(body), headers: headers, encoding: encoding).then((http.Response response) {
      final String res = response.body;
      final int statusCode = response.statusCode;

      _updateCookie(response);

      if (statusCode < 200 || statusCode > 400) {
        throw Exception("Error while fetching data");
      }
      return _decoder.convert(res);
    });
  }
}

2
如果没问题的话,我会使用dio和cookiejar。只需要在你的pubspec.yaml中添加这些依赖项即可。
dependencies:
  dio: ^4.0.4
  dio_cookie_manager: ^2.0.0
  cookie_jar: ^3.0.1

以下是一个使用它的示例。确保将其作为Flutter脚本而不是Dart脚本运行。

import 'package:cookie_jar/cookie_jar.dart';
import 'package:dio/dio.dart';
import 'package:dio_cookie_manager/dio_cookie_manager.dart';


void main() async {
  var dio =  Dio(BaseOptions(
      connectTimeout: 10000,  // in ms
      receiveTimeout: 10000,
      sendTimeout: 10000,
      responseType: ResponseType.plain,
      followRedirects: false,
      validateStatus: (status) { return true; }
  ));   // some dio configurations

  dio.interceptors.add(CookieManager(CookieJar()));

  var firstResponse = await dio.get(
      "https://somewebsite.com/get_login_info");
  print(firstResponse.data);

  var loginResponse = await dio.post(
      "https://somewebsite.com/login",
      data: FormData.fromMap(
          {
            'username': 'YourUsername',
            'password': 'YourPassword',
          }
      ));  // cookies are automatically saved
  print(loginResponse.statusCode);

  var nextResponse = await dio.get(
      "https://somewebsite.com/get_login_info");
  print(nextResponse.data);
}

一个示例输出:

{"is_logged_in": 0}
302
{"is_logged_in": 1}

1
我找到了最佳解决方案来处理重定向的cookies。
import 'dart:convert';
import 'dart:io';

class CustomHTTPClient{
  final HttpClient _client = new HttpClient();
  Map<String, String> _cookies = Map();

  CustomHTTPClient(){
    _client.connectionTimeout = Duration(seconds: 10);
  }

  Future<String> get(String url, {int maxRedirect = 3}) async {
    final parsedUrl = Uri.parse(url);
    return await _client.getUrl(parsedUrl)
        .then((HttpClientRequest request) {
      request.followRedirects = false;
      _beforeRequest(request);
      return request.close();
    }).then((HttpClientResponse response) async {
      _afterResponse(response);
      if(response.isRedirect && maxRedirect > 0){
        return await response.drain().then((value) => get(parsedUrl.resolve(response.headers.value('location')).toString(), maxRedirect: maxRedirect - 1));
      }
      return response.transform(utf8.decoder).join();
    }).catchError((error, stack){
      print(error);print(stack);
    });
  }

  void _beforeRequest(HttpClientRequest request){
    request.headers.set(HttpHeaders.acceptEncodingHeader, 'gzip, deflate, br');

    // Set cookie
    final String rawCookies = _cookies.keys.map((String name) => '$name=${_cookies[name]}').join('; ');
    if(rawCookies.isNotEmpty) request.headers.set(HttpHeaders.cookieHeader, rawCookies);
  }

  void _afterResponse(HttpClientResponse response){
    response.headers.forEach((String name, List<String> values){
      if(name == 'set-cookie'){ // Get cookies for next request
        values.forEach((String rawCookie){
          try{
            Cookie cookie = Cookie.fromSetCookieValue(rawCookie);
            _cookies[cookie.name] = cookie.value;
          } catch(e){
            final List<String> cookie = rawCookie.split(';')[0].split('=');
            _cookies[cookie[0]] = cookie[1];
          }
        });
        return false;
      }
    });
  }
}

0
如果你只需要一个格式,那么这就是它:
Map<String, String> headers = {
   'cookie': 'cookie1=$data1;cookie2=data2;cookie3=data3'
};

可以通过调用cookie1、2、3来访问


0

根据您使用的是Flutter Web还是移动端,获取cookie有不同的方法

对于Flutter Web:

  • 在前端将凭据设置为true
  • 在服务器上,在标头中添加"'Access-Control-Allow-Credentials', true",我在后端使用Dart(Alfred框架)
  • 检查来自您的服务器的源是否与前端的主机和端口匹配

您的浏览器会自动重新发送cookie。

您可以使用以下命令在启动应用程序时定义特定端口 - "flutter run -d chrome --web-port 5555"

但是对于移动设备,您需要做一些技巧

我使用Dio包轻松定义onResponse/onRequest函数和有条件的导入来避免编译失败。(很遗憾withCredentials选项只适用于Web)

如果您想使用默认http类

只需创建您自己的onResponse/onRequest函数即可

NetworkConfig.dart

import 'package:dio/dio.dart';

import '../../../constants/url_paths.dart';
import 'get_net_config.dart'
    if (dart.library.io) 'mobile_net_config.dart'
    if (dart.library.html) 'web_net_config.dart';

class NetworkConfig {
  final _client = getClient()
    ..options = BaseOptions(
      baseUrl: url,
      connectTimeout: const Duration(seconds: 5),
      receiveTimeout: const Duration(seconds: 6),
    );

  Dio get client => _client;

  final Map<String, String> headers = <String, String>{
    'Content-Type': 'application/json'
  };
}

我使用另一个类来执行我的get、post...,该类继承了NetworkConfig

get_network_config.dart

import 'package:dio/dio.dart';

    Dio getClient() => throw UnsupportedError('[Platform ERROR] Network client');

web_network_config.dart

import 'package:dio/browser.dart';
import 'package:dio/dio.dart';

Dio getClient() =>
    Dio()..httpClientAdapter = BrowserHttpClientAdapter(withCredentials: true);

mobile_network_config.dart

import 'dart:io';
import 'package:<projet_name>/data/data.dart';
import 'package:dio/dio.dart';

// CLIENT
Dio getClient() => Dio()
  ..interceptors.add(InterceptorsWrapper(
    onRequest: (options, handler) async {
      options.headers['cookie'] = await localData.read('cookie');

      return handler.next(options);
    },
    onResponse: (response, handler) {
      response.headers.forEach((name, values) async {
        if (name == HttpHeaders.setCookieHeader) {
          final cookieMap = <String, String>{};

          for (var c in values) {
            var key = '';
            var value = '';

            key = c.substring(0, c.indexOf('='));
            value = c.substring(key.length + 1, c.indexOf(';'));

            cookieMap[key] = value;
          }

          var cookiesFormatted = '';

          cookieMap
              .forEach((key, value) => cookiesFormatted += '$key=$value; ');

          await localData.write('cookie', cookiesFormatted);

          return;
        }
      });

      return handler.next(response);
    },
  ));

localData是我的Flutter包装器,用于持久化本地cookie


如果您使用默认的Client()类,也可以像这样设置凭据。
import 'package:http/http.dart';

Client getClient() => BrowserClient()..withCredentials = true;

0
我创建了一个单例类,因为对我来说,如果不使用单例模式创建ApiService的实例,就会重新初始化所有内容。
我还创建了一个自定义的post函数,用于处理项目中的所有post请求。类似地,也可以创建get和其他函数。
api_service.dart(单例类)
import 'package:http/http.dart' as http;

class ApiService {
  static final ApiService _singleton = ApiService._internal();
  factory ApiService() {
    return _singleton;
  }

  ApiService._internal();

  String? sessionCookie;

  Future<void> setSessionCookie(http.Response response) async {
    String? rawCookie = response.headers['set-cookie'];
    if (rawCookie != null) {
      int index = rawCookie.indexOf(';');
      sessionCookie = (index == -1) ? rawCookie : rawCookie.substring(0, index);
    }
  }

  Future<http.Response> post(String url, Map<String, String?> body) async {
    try {
      final response = await http.post(
        Uri.parse(url),
        headers: {'Cookie': sessionCookie ?? ''},
        body: body,
      );

      if (response.statusCode == 200) {
        setSessionCookie(response); // Updating the session cookie here
      }

      return response;
    } catch (e) {
      throw Exception('Error making POST request: $e');
    }
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接