Flutter网络API请求插件simple_http_api的使用

发布于 1周前 作者 htzhanglong 来自 Flutter

Flutter网络API请求插件simple_http_api的使用

pub package GitHub Repo stars

本包扩展了官方的 http 包,并简化了 dio 包的核心功能。它是一个开箱即用的包,可以轻松创建 get/post/put/patch/delete 请求并上传文件,避免导入冗余函数。

功能

  1. 请求重试: 允许通过设置一个 Duration 来重试请求。如果在指定的时间内未收到可接受的响应,则请求将被终止并重新开始。

  2. 请求取消: 取消请求可以与重试功能一起工作。

  3. 文件上传: 支持在 Isolate 中上传文件。目前仅支持上传过程中的 onUploadProgress 回调。参见 上传示例

  4. 接收响应数据作为流: 支持将响应数据作为流接收。参见 接收响应数据作为流示例

  5. 开箱即用,参见示例。

使用方法

GET 请求

import 'package:simple_http_api/simple_http_api.dart';

void _get() async {
  final url = Uri.parse("http://127.0.0.1:8080");

  try {
    final res = await Api.get(
      url,
      headers: {"accept": "application/json"},
      cancelToken: TimingToken(Duration(seconds: 2)),
      options: ConnectionOption(
        connectionTimeout: Duration(seconds: 1),
        sendTimeout: Duration(seconds: 1),
        receiveTimeout: Duration(seconds: 3),
      ),
    );
    print(res);
  } catch (e) {
    print(e);
  }
}

重试GET请求

Future<void> _retryGet([int? delayMs]) async {
  final delay = delayMs != null && delayMs > 0 ? "?delay=$delayMs" : "";

  final url = Uri.parse("http://127.0.0.1:8080$delay");

  try {
    final res = await Api.get(
      url,
      // headers: {"accept": "application/json"},
      // cancelToken: TimingToken(Duration(seconds: 3)),
      options: ConnectionOption(
        connectionTimeout: Duration(seconds: 1),
        sendTimeout: Duration(seconds: 1),
        // receiveTimeout: Duration(seconds: 2),
      ),
      retryConfig: RetryConfig(
        retryTimeout: Duration(seconds: 5),
        retries: 3,
        // retryWhenException: (e) => e.type != ErrorType.other,
        // retryWhenStatus: (code) => code >= 300,
      ),
    );
    print(res);
  } catch (e) {
    print(e);
  }
}

POST 请求

import "dart:convert";
import 'package:simple_http_api/simple_http_api.dart';

void _post_() async {
  final url = Uri.parse("http://127.0.0.1:8080");

  final data = {
    "hello": "api",
    "delay": "4000",
    "list": [100],
  };

  try {
    final res = await Api.post(
      url,
      headers: {
        "accept": "application/json",
        "content-type": "application/json",
      },
      cancelToken: TimingToken(Duration(seconds: 2)),
      body: json.encode(data),
      options: ConnectionOption(
        connectionTimeout: Duration(seconds: 1),
        sendTimeout: Duration(seconds: 1),
        receiveTimeout: Duration(seconds: 3),
      ),
    );
    print(res);
  } catch (e) {
    print(e);
  }
}

重试POST请求

Future<void> _retryPost() async {
  final url = Uri.parse("http://127.0.0.1:8080");

  final data = {
    "hello": "api",
    "delay": 2000,
    "list": [100],
  };

  try {
    final res = await Api.post(
      url,
      headers: {
        "accept": "application/json",
        "content-type": "application/json",
      },
      body: json.encode(data),
      // cancelToken: TimingToken(Duration(seconds: 5)),
      options: ConnectionOption(
        connectionTimeout: Duration(seconds: 1),
        sendTimeout: Duration(seconds: 1),
        // receiveTimeout: Duration(seconds: 2),
      ),
      retryConfig: RetryConfig(
        retryTimeout: Duration(seconds: 3),
        retries: 3,
        // retryWhenException: (e) => e.type != ErrorType.other,
        retryWhenStatus: (code) => code >= 300,
      ),
    );
    print(res);
  } catch (e) {
    print(e);
  }
}

文件上传

import 'dart:async';
import 'package:simple_http_api/simple_http_api.dart';

void main() async {
  await _uploadSingle("./assets/demo.mp4");
}

Future<void> _uploadSingle(String path) async {
  final url = Uri.parse("http://127.0.0.1:8080/upload/single");
  final file = await FormData.fileFromPath(path, field: "single");
  final formData = FormData();

  formData.addFile(file);

  formData.addFields({"upload": "test"});

  try {
    final res = await Api.upload(url, formData,
        cancelToken: TimingToken(Duration(seconds: 3)),
        headers: {
          "content-type": "application/json",
        },
        onUploadProgress: (sent, total) =>
            print("total: $total, sent: $sent, percent: ${sent / total}"));
    print(res);
  } catch (e) {
    print("e");
  }
}

多文件上传

Future<void> _uploadMulti() async {
  final url = Uri.parse("http://127.0.0.1:8080/upload/multi");
  final file1 =
      await FormData.fileFromPath("./assets/demo.mp4", field: "multi");

  final file2 =
      await FormData.fileFromPath("./assets/demo.png", field: "multi");

  final formData = FormData();

  formData.addFile(file1);
  formData.addFile(file2);

  formData.addFields({"upload": "test"});

  try {
    final res = await Api.upload(
      url,
      formData,
      cancelToken: TimingToken(Duration(seconds: 3)),
      headers: {
        "content-type": "application/json",
      },
    );
    print(res);
  } catch (e) {
    print("e");
  }
}

接收响应数据作为流

import 'dart:convert';
import 'package:simple_http_api/simple_http_api.dart';

void main() async {
  final headers = {
    "Content-Type": "application/json",
    "Authorization": "Bearer &lt;token&gt;",
  };

  final url = Uri.parse("https://api.openai.com/v1/completions");

  final data = {
    "model": "text-davinci-003",
    "prompt": "give 5 words",
    "max_tokens": 256,
    "stream": true,
  };

  final eventSource = EventSource(url, ApiMethod.post);
  eventSource.setHeaders(headers);

  final cancelToken = TimingToken(Duration(seconds: 2));
  final stream =
      eventSource.send(body: json.encode(data), cancelToken: cancelToken);

  stream.listen(
    (event) {
      if (eventSource.isWeb) {
        print(event.chunk as String);
      } else {
        final encoding = event.getEncoding();

        print(encoding.decode(event.chunk as List<int>));
      }
    },
    onError: (err) => print(err),
    onDone: eventSource.close,
  );
}

创建请求

用户必须使用 try-catch 捕获 ApiError,以防没有返回预期的 ApiResponse(例如,请求超时或响应不符合预期)。

  1. 需要指定 headers 中的 content-type 字段。如果不指定,会根据 body 的类型自动选择不同的媒体类型:

    • 如果 bodyString 类型 -> text/plain
    • 如果 bodyMap<String, String> 类型 -> application/x-www-form-urlencoded
    • 如果 bodyList<int> 类型 -> 不应用任何媒体类型
    • 如果以上情况都不适用,设置 body 时会抛出 ArgumentError
  2. (可选)指定一种 CancelToken 来确定是否取消此请求。

    • TimingToken: 这个令牌会在创建 HttpRequest/XMLHttpRequest 之前开始计时。当其令牌过期时,它会调用 cancel() 完成请求。因此,HttpRequest/XMLHttpRequest 将被中止,并抛出 ErrorType.cancel
    • RetryToken: 如果没有提供 RetryConfig,它将表现得像 TimingToken。如果提供了 RetryConfig,它将与主令牌一起工作以确定是否取消当前请求并开始重试。
  3. (可选)指定 ConnectionOption 来控制请求的不同阶段的持续时间。

    • (Web): 所有三种类型的超时都会尝试完成 Completer 并在其中一个验证成功后强制中止此 XMLHttpRequest
      • connectionTimeoutXHR.readyState < 1 后的持续时间内验证成功
      • sendTimeoutXHR.readyState < 3 和设置了接收开始时间后的持续时间内验证成功
      • receiveTimeoutonLoadStart 被调用后的持续时间内验证成功
    • (移动设备): HttpClient 会在三个超时中的任何一个验证成功后被强制关闭。
      • connectionTimeout 在创建 HttpClient 时设置
      • sendTimeout 在启动 addStreamHttpClientRequest 时激活
      • receiveTimeout 分为两个阶段:1) 尝试获取响应时调用 HttpClientRequest.close() 2) 每次接收到数据块时

ConnectionOption 如何与 CancelToken 协同工作

通常,CancelToken 可以让用户决定 1) 请求预计完成的总时间 2) 需要在某些意外/故意发生的情况下忽略/取消请求。

ConnectionOption 让用户确定请求的不同阶段预计完成的时间。如果某个阶段的请求花费的时间超过给定的超时时间,它将被直接中止/取消。

ConnectionOptionCancelToken 将分别验证自己,并尝试在任何一个验证成功时中止/取消请求。

重试机制如何实现

RetryConfig.retries 限制最大重试次数,而 RetryConfig.retryInterval 限制两次请求之间的间隔。

用户有两种方式停止重试:

  1. 提供一个 CancelToken 来控制由 _RetryClient 创建的 RetryToken(不可直接访问)。一旦 CancelToken 过期,重试将停止,并且如果适用,当前请求将被中止。
  2. RetryConfig.retryWhenExceptionRetryConfig.retryWhenStatus 一起工作,确定在达到最大重试次数之前是否继续重试。

上述两种方式可以一起工作。

注意:RetryConfig.retryInterval 表示下一次请求将在上一次请求未返回响应时创建并中止前一次请求,而不是等待 retryInterval 后再创建下一次请求。

调查

  1. 在接收超时时释放资源的最佳方式是什么?
    • client.close(force: true);
    • 或者使用:
      response
          .detachSocket()
          .then((socket) => socket.destroy())
          .catchError(
        (err) {
          print("error on detaching socket: $err");
        },
        test: (error) => true,
      );
      

更多关于Flutter网络API请求插件simple_http_api的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter网络API请求插件simple_http_api的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,下面是一个使用Flutter网络API请求插件simple_http_api的示例代码。这个插件可以用来简化HTTP请求的发送和处理。

首先,你需要在你的pubspec.yaml文件中添加这个依赖:

dependencies:
  flutter:
    sdk: flutter
  simple_http_api: ^x.y.z  # 请将x.y.z替换为最新的版本号

然后运行flutter pub get来获取依赖。

接下来,让我们编写一个Flutter应用来演示如何使用simple_http_api插件。

1. 导入依赖

在你的Dart文件中(例如main.dart),导入所需的包:

import 'package:flutter/material.dart';
import 'package:simple_http_api/simple_http_api.dart';

2. 配置HTTP客户端

你可以创建一个HTTP客户端实例,并配置一些基础设置,如baseURL和超时时间:

final httpApi = HttpApi(
  baseUrl: 'https://api.example.com',  // 替换为你的API基础URL
  timeout: 10,  // 请求超时时间,单位为秒
);

3. 发送GET请求

Future<void> fetchData() async {
  try {
    final response = await httpApi.get('/endpoint');  // 替换为你的API端点
    if (response.isSuccessful) {
      final data = await response.data;
      print('Data: $data');
    } else {
      print('Error: ${response.message}');
    }
  } catch (e) {
    print('Exception: $e');
  }
}

4. 发送POST请求

Future<void> postData() async {
  try {
    final body = {
      'key1': 'value1',
      'key2': 'value2',
    };
    final response = await httpApi.post('/endpoint', body: body);  // 替换为你的API端点
    if (response.isSuccessful) {
      final data = await response.data;
      print('Response Data: $data');
    } else {
      print('Error: ${response.message}');
    }
  } catch (e) {
    print('Exception: $e');
  }
}

5. 在UI中使用

最后,将这些功能集成到你的Flutter UI中。例如,可以创建两个按钮,一个用于发送GET请求,另一个用于发送POST请求:

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Simple HTTP API Example'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              ElevatedButton(
                onPressed: fetchData,
                child: Text('Fetch Data (GET)'),
              ),
              ElevatedButton(
                onPressed: postData,
                child: Text('Post Data (POST)'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

完整代码

import 'package:flutter/material.dart';
import 'package:simple_http_api/simple_http_api.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  final httpApi = HttpApi(
    baseUrl: 'https://api.example.com',  // 替换为你的API基础URL
    timeout: 10,  // 请求超时时间,单位为秒
  );

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Simple HTTP API Example'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              ElevatedButton(
                onPressed: () async {
                  try {
                    final response = await httpApi.get('/endpoint');  // 替换为你的API端点
                    if (response.isSuccessful) {
                      final data = await response.data;
                      print('Data: $data');
                    } else {
                      print('Error: ${response.message}');
                    }
                  } catch (e) {
                    print('Exception: $e');
                  }
                },
                child: Text('Fetch Data (GET)'),
              ),
              ElevatedButton(
                onPressed: () async {
                  try {
                    final body = {
                      'key1': 'value1',
                      'key2': 'value2',
                    };
                    final response = await httpApi.post('/endpoint', body: body);  // 替换为你的API端点
                    if (response.isSuccessful) {
                      final data = await response.data;
                      print('Response Data: $data');
                    } else {
                      print('Error: ${response.message}');
                    }
                  } catch (e) {
                    print('Exception: $e');
                  }
                },
                child: Text('Post Data (POST)'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

请确保替换示例代码中的URL和端点为你实际的API信息。这样,你就可以使用simple_http_api插件在Flutter应用中发送HTTP请求了。

回到顶部