如何在 Flutter 上使用 cookie 发出 http 请求?

Posted

技术标签:

【中文标题】如何在 Flutter 上使用 cookie 发出 http 请求?【英文标题】:How do I make an http request using cookies on flutter? 【发布时间】:2019-02-13 21:39:22 【问题描述】:

我想在正确处理 cookie 的同时向远程服务器发出 http 请求(例如,存储服务器发送的 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;

【问题讨论】:

【参考方案1】:

这是一个如何获取会话 cookie 并在后续请求中返回它的示例。您可以轻松调整它以返回多个 cookie。创建一个Session 类,并通过它路由你所有的GETs 和POSTs。

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);
    
  

【讨论】:

只是问你将如何在小部件中调用它。 谢谢,工作得很好!我使用了这个解决方案,我只是将类更改为单例,这样我就可以在我的小部件中访问一个实例 为什么只得到 ' 之前的第一个子字符串? ' 的饼干?例如,在这个 cookie 中:“OCENTRIC_COOKIE=1; path=/;Secure;HttpOnly”,在 ' 之后是什么; ' 不重要吗? 根据 RFC,所有标头都不区分大小写,尽管许多服务器不兼容。为了便于比较,Dart 强制它们全部小写。 @daraul 是在分号(如果有的话)后面切掉一点 - 请参阅此处的讨论:***.com/questions/50299253/…【参考方案2】:

我改进了 Richard Heap 的解决方案,使其能够处理多个“Set-cookies”和多个 cookie。

在我的例子中,服务器返回多个“Set-cookies”。 http 包将所有 set-cookies 标头连接到一个标头中,并用逗号 (',') 分隔。

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 感谢这对我有帮助。在 '=' 上拆分 rawCookie 时有一个小错误;当拆分中有空字符串时,它不起作用。以下修复帮助了我。 rawCookie.split('=').where((s) => s.isNotEmpty).toList(growable: false) 在比较路径和过期时也忽略大小写 - if (key.toLowerCase() != 'path' && key.toLowerCase() != 'expires') return key: value; 当cookie的值部分有任何'='时,答案不考虑。我必须更改 _setCookie 方法: if (rawCookie.length > 0) int idx = rawCookie.indexOf("="); if (idx >= 0) var key = rawCookie.substring(0, idx).trim(); var value = rawCookie.substring(idx+1).trim(); if (key == 'path' || key == 'expires' || key == 'domain' || key == 'SameSite') 返回;饼干[键] =值; 【参考方案3】:

我发布了一个名为 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

【讨论】:

非常感谢先生!你的包裹非常适合我! 嗨,我正在使用来自这里 github.com/zino-app/graphql-flutter 的 graphql 服务器和 flutter_graphql 包。关于如何使用这个包的任何想法 @Jossef 嗨,有没有办法在不终止第一个请求的情况下发送第二个请求?我在处理过期时间“会话”的 cookie 时遇到问题 @CharukaHS 请打开一个问题 -> github.com/jossef/requests/issues 添加您的请求响应详细示例 好像有旧的依赖不能用这个【参考方案4】:

我也找到了处理重定向 cookie 的最佳解决方案

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;
      
    );
  

【讨论】:

【参考方案5】:

我已将 larly's answer 迁移到 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);
    );
  

【讨论】:

【参考方案6】:

如果没有问题,我会使用 dio 和 cookiejar。

只需在您的pubspec.yaml 中添加这些依赖项:

dependencies:
  dio: ^4.0.4
  dio_cookie_manager: ^2.0.0
  cookie_jar: ^3.0.1

这是一个使用它的示例。确保将其作为颤振脚本而不是 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

【讨论】:

以上是关于如何在 Flutter 上使用 cookie 发出 http 请求?的主要内容,如果未能解决你的问题,请参考以下文章

Flutter Cubit 如何发出多个状态变化

如何在 Flutter 中发出 API 请求

如何在 Flutter 中使用 JSON 正文发出 http DELETE 请求?

如何在 Flutter/Dart 中使用 url 编码的标头和正文发出 HTTP POST 请求

我如何对嵌套在flutter中的json发出发布请求?

使用存储和 cookie(不使用 Firebase)在 Flutter 中进行身份验证?