Flutter API调用代理插件api_agent的使用

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

Flutter API调用代理插件api_agent的使用

api_agent 是一个技术无关的API绑定库,适用于您的全栈Dart应用。

主要特性

  1. api_agent 通过从单一源定义生成类型安全的客户端和服务端绑定,来桥接Dart前端和后端之间的服务缺口。它会自动保持客户端和服务端的API定义同步。
  2. 这个包只关注API的结构,而不是使用的具体技术和协议。因此它是完全技术无关且协议无关的,并且您对数据传输层有完全控制权。它提供了针对常见协议(如HTTP)的现成实现,或者您可以根据需要实现自己的协议支持(例如 Websockets, MQTT, GRPC, GraphQL 或任何专有协议)。

目录

概述

在共享位置定义您的API就像定义一个抽象类一样简单。

@ApiDefinition()
abstract class MyApi {
  Future<MyResult> myApiEndpoint();
}

在客户端访问API就像调用一个方法一样简单。

void main() async {
  var client = MyApiClient( // 从您的API定义生成
    HttpApiClient( // 现成的HTTP客户端
      domain: "http://my.domain.com", // HTTP客户端特定选项
    ),
  );
  
  var result = await client.myApiEndpoint(); // 从您的API定义生成的方法
}

在服务端实现API就像实现一个抽象方法一样简单。

void main() async {
  var server = await serve( // 从包:shelf
    ShelfApiRouter([ // 现成的shelf路由器
      MyApiEndpointImpl(),
    ]),
    InternetAddress.anyIPv4, port
  );
}

class MyApiEndpointImpl extends MyApiEndpoint { // 从您的API定义生成
  @override
  Future<MyResult> myApiEndpoint(ApiRequest request) {
    // 您的实现在这里
  }
}

设置与入门

对于使用api_agent的全栈Dart应用,您需要以下设置:

首先创建一个新的工作空间文件夹:

mkdir my_app && cd my_app

首先创建一个shared Dart项目,这将包含前后端都会使用的所有代码。在这种情况下,我们将API定义放在这个项目中。

dart create my_app_shared
cd my_app_shared

这将创建一个简单的Dart应用项目。由于我们希望将其作为共享库使用,所以我们不需要生成的bin目录,而是需要一个包含definitions.api.dartlib目录。

rm -r bin
mkdir lib && touch lib/definitions.api.dart

我们还需要将api_agent作为依赖项添加,同时将build_runner作为开发依赖项添加。

dart pub add api_agent
dart pub add build_runner --dev

接下来我们在创建的文件中添加我们的API定义:

import 'package:api_agent/api_agent.dart';

@ApiDefinition()
abstract class GreetApi {
  Future<String> greet(String name);
}

最后,在共享项目中运行build_runner以生成前后端所需的API绑定。这将生成definitions.client.dartdefinitions.server.dart,分别用于前后端。

dart run build_runner build

前端项目设置

接下来我们创建前端项目。这可以是一个命令行应用程序,也可以是一个使用Dart编写的Flutter应用或网站。为了简化起见,我们将创建一个简单的命令行应用,但所有客户端应用的用法都是相同的。

cd .. # 确保我们在工作区目录中
dart create my_app_frontend

我们同样需要将api_agent作为依赖项添加。此外,我们还需要将my_app_shared项目作为本地路径依赖项添加。

cd my_app_frontend
dart pub add api_agent
dart pub add my_app_shared --path=../my_app_shared

现在打开生成的bin/my_app_frontend.dart并更改其内容如下。这将导入共享包中的生成API绑定,并使用它设置基本的HTTP客户端。

import 'package:api_agent/clients/http_client.dart';
import 'package:my_app_shared/definitions.client.dart';

void main(List<String> args) async {
  var client = GreetApiClient( 
    HttpApiClient(domain: "http://localhost:8080"),
  );

  var result = await client.greet(args[0]);
  print(result);
}

后端项目设置

最后我们设置后端项目。这可以使用任何结构或包,但为了简化起见,我们将使用标准的package:shelf应用。

cd .. # 确保我们在工作区目录中
dart create -t server-shelf my_app_backend

像往常一样,我们需要在api_agent和我们的my_app_shared项目上添加所需的依赖项。

cd my_app_backend
dart pub add api_agent
dart pub add my_app_shared --path=../my_app_shared

接下来我们修改生成的bin/server.dart。我们可以保留大部分设置,只需插入自定义路由器及其API实现。因此将第7到19行替换为以下内容:

import 'package:api_agent/servers/shelf_router.dart';
import 'package:my_app_shared/definitions.server.dart';

final _router = ShelfApiRouter([GreetApiImpl()]);

class GreetApiImpl extends GreetApiEndpoint {
  @override
  String greet(String name, ApiRequest _) {
    return 'Hello $name.';
  }
}

使用

正如在“设置与入门”部分所展示的那样,使用api_agent的全栈应用有三个主要部分:

  • 共享项目中的API定义
  • 前端项目的API客户端
  • 后端项目的API端点

API定义

通过使用@ApiDefinition()注解在抽象类上定义API定义。该类中的抽象方法定义了API支持的端点。一个方法可以具有任意数量的必需或可选参数,并且必须返回任何类型的Future

@ApiDefinition()
abstract class GreetApi {
  Future<String> greet(String name);
  
  Future<MyResult> compute(MyData data, {bool? someOptionalParam});
}

一个ApiDefinition可以通过定义具有任何名称的抽象getter来包含任意数量的嵌套子ApiDefinition。返回类型必须是另一个ApiDefinition

@ApiDefinition()
abstract class MainApi {
  UserApi get users;
}

@ApiDefinition()
abstract class UserApi {
  Future<List<User>> list();
}

如何处理嵌套的ApiDefinition取决于协议实现。例如,HTTP实现会根据嵌套的API定义和目标方法构造请求URL。在上述情况下,调用MainApiClient().users.list()将导致URL路径为/users/list

API客户端

api_agent为每个API定义生成API客户端。要使用客户端,实例化它时传入一个协议客户端作为唯一参数:

var client = GreetApiClient( // 从您的API定义生成
  HttpApiClient(), // 特定协议客户端
);

var result = await client.greet();

api_agent已经包含了以下客户端:

HttpApiClient

这将使用http包向您的API发出请求。

  • 当方法前缀为get时(例如getUsers),将使用GET,否则使用POST
  • 对于GET请求,参数作为查询参数编码
  • 对于POST请求,参数作为JSON有效载荷发送
  • URL由类名和方法名按以下方式构造:
    • 类名中的Api后缀在URL中被忽略(GreetApi -> /greet
    • 方法名中的get前缀在URL中被忽略(getUsers() -> /users
    • 方法名和类名被转换为蛇形命名(myMethodName() -> /my_method_name

HttpApiClient构造函数接受:

  • domain,您的API托管的位置
  • 可选的path,您的API挂载的位置,默认为/
  • 可选的ApiProvider列表
  • 可选的ApiCodec作为默认值,如果API定义中未定义

ApiProviders

ApiProviders可以在请求发送之前对其进行拦截和修改。一个常见的用例是向请求添加认证头:

class AuthProvider extends ApiProvider<HttpApiRequest> {
  @override
  FutureOr<HttpApiRequest> apply(HttpApiRequest request) {
    return request.change(headers: {'Authorization': 'Bearer my-bearer-token'});
  }
}

自定义API客户端编写

要定义一个使用您选择的协议的自定义客户端,只需实现ApiClient接口:

class MyProtocolApiClient implements ApiClient {
  @override
  ApiClient mount(String prefix, [ApiCodec? codec]) {
    
  }
   
  @override
  Future<T> request<T>(String endpoint, Map<String, dynamic> params) {

  }
}

mount()方法应该返回一个新的API客户端实例,尊重给定的前缀和编解码器。

request()方法应该通过所选协议和技术实际执行API调用。它接收请求的端点名称(调用客户端方法的名称)和提供的参数映射。一个标准实现可能包括:

  1. 构造一个ApiRequest实例,可能是特定协议的子类,如HttpApiRequest
  2. 通过request.apply(providers)应用任何给定的ApiProviders
  3. 执行请求(特定于协议的实现)
  4. 返回结果,可能使用codec.decode<T>(result)进行解码

通常最好查看现有API客户端的实现,如包含的HttpApiClient

API端点

api_agent为每个ApiDefinition生成ApiEndpointApiEndpoint需要在服务器端实现,并传递给一个ApiBuilder,后者例如启动一个HTTP服务器并将传入的请求传递给您的端点。

要实现您的端点,您需要做以下操作:

首先,我们上面的API定义将生成一个GreetEndpoint,它期望一个形式的实现FutureOr<String> Function(String name, ApiRequest request)

有许多方法可以实现一个端点。

  1. 传递处理程序函数
var greetEndpoint = GreetEndpoint.from((name, r) {
  return 'Hello $name.';
});
  1. 扩展抽象类
var greetEndpoint = GreetEndpointImpl();

class GreetEndpointImpl extends GreetEndpoint {
  @override
  FutureOr<String> greet(String name, ApiRequest request) {
    return 'Hello $name.';
  } 
}

您可以根据具体情况自由选择这两种选项之一。

接下来,api_agent还将生成一个GreetApiEndpoint,它期望一个GreetEndpoint作为其子节点。您可以再次选择直接提供端点,或者实现抽象类:

  1. 传递子端点
var greetApi = GreetApiEndpoint.from(
  greet: greetEndpoint,
);
  1. 扩展抽象类
var greetApi = GreetApiEndpointImpl();

class GreetApiEndpointImpl extends GreetApiEndpoint {
  @override
  FutureOr<String> greet(String name, ApiRequest request) {
    return 'Hello $name.';
  }
}

这可能看起来有些混乱,但它给您提供了选择合适的一致性级别的自由。对于小型简单的API,您可能不希望为每个端点创建一个自定义类。但对于较大且复杂的API,您可能会发现将逻辑分离出来更合适。

API中间件

在具有嵌套内部API的更复杂API中,您的端点将形成树状结构:

var myApi = MyApiEndpoint.from(
  users: UsersEndpoint.from(
    list: ...,
    getById: ...,
  ),
  publications: PublicationsEndpoint.from(
    articles: ArticlesEndpoint.from(
      list: ...,
      ...
    ),
  ),
)

您可以在树中的任何位置添加ApiMiddleware,以监控或保护所选子树中的端点请求。一个典型的用例是验证请求用户。

通过实现ApiMiddleware接口来实现自定义的ApiMiddleware

class AuthMiddleware implements ApiMiddleware {
  @override
  FutureOr<dynamic> apply(covariant ShelfApiRequest request, EndpointHandler next) {
    String? token = request.headers['Authorization'];
    if (validateToken(token)) {
      return next(request);
    } else {
      throw ApiException(401, 'Authentication token is invalid.');
    }
  }
}

然后,使用ApplyMiddleware端点插入您的中间件:

var myApi = MyApiEndpoint.from(
  users: ApplyMiddleware(
    middleware: AuthMiddleware(),
    child: UserEndpoint.from( // 中间件应用于所有子端点
      ...
    ),
  ),
  publications: PublicationsEndpoint.from( // 中间件不应用于端点
    ... 
  ),
);

API构建器

ApiBuilder消耗您的API端点并构建客户端可以连接的通信通道。api_agent已经包含了以下构建器:

ShelfApiRouter

这将使用shelf_router包构建一个路由器,该路由器可以与shelf包一起使用,并例如传递给serve

它旨在与HttpApiClient一起使用,并具有相同的要求规则。

要使用它,只需将您的API端点传递给ShelfApiRouter构造函数并将其用作shelf处理器:

void main() {
  var router = ShelfApiRouter([greetApi]);
  
  serve(router, InternetAddress.anyIPv4, port: 8080);
}

自定义API构建器编写

要定义一个使用您选择的协议的自定义API构建器,只需实现ApiBuilder接口:

class MyProtocolApiBuilder implements ApiBuilder {
  @override
  void mount(String prefix, List<ApiEndpoint> children) {
    // 在[prefix]下安装[children]
    // 应调用[ApiEndpoint.build()]为每个子项创建新的API构建器实例
  }
  
  @override
  void handle(String endpoint, EndpointHandler handler) {
    // 注册此[endpoint]的[handler]
  }
}

之后,您应该调用您的根ApiEndpoint(s)的build(ApiBuilder builder)方法,并提供您的自定义ApiBuilder实例。这些将随后调用mount()handle()以与您的API注册。

建议查看现有API构建器的实现,如包含的ShelfApiRouter

示例代码

下面是一个完整的示例代码,展示了如何使用api_agent在Flutter应用中调用API。

// 共享API定义

import 'package:api_agent/api_agent.dart';

@ApiDefinition()
abstract class GreetApi {
  Future<String> greet(String name);
}

// 客户端代码

import 'package:api_agent/clients/http_client.dart';

Future<void> clientMain() async {
  var client = GreetApiClient(
    HttpApiClient(domain: 'http://localhost:8080'),
  );

  var result = await client.greet('James');
  print(result); // 打印 'Hello James.'
}

// 服务端代码

import 'package:api_agent/servers/shelf_router.dart';
import 'package:shelf/shelf_io.dart';

void serverMain() async {
  var api = ShelfApiRouter([
    GreetApiEndpoint.from(
      greet: GreetEndpoint.from((name, _) {
        return 'Hello $name.';
      }),
    ),
  ]);

  serve(api, InternetAddress.anyIPv4, 8080);
}

测试

要测试您的应用,可以在两个单独的终端窗口中启动服务器和客户端:

# 终端1: 运行服务器
cd my_app_backend && dart run bin/server.dart
# 终端2: 运行客户端
cd my_app_frontend && dart run bin/my_app_frontend.dart James

更多关于Flutter API调用代理插件api_agent的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter API调用代理插件api_agent的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是一个关于如何在Flutter中使用代理插件 api_agent 进行API调用的代码示例。这个示例假定你已经安装并配置好了 api_agent 插件。

首先,确保你已经在 pubspec.yaml 文件中添加了 api_agent 依赖:

dependencies:
  flutter:
    sdk: flutter
  api_agent: ^最新版本号  # 替换为实际的最新版本号

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

接下来,你可以在你的 Flutter 项目中配置和使用 api_agent。以下是一个简单的示例,展示了如何使用该插件进行API调用:

1. 配置代理设置

首先,你需要配置代理设置。这通常在应用的初始化阶段完成。

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

void main() {
  // 初始化 ApiAgent 配置
  ApiAgentConfig config = ApiAgentConfig(
    baseUrl: 'https://api.example.com', // 替换为你的API基础URL
    timeout: Duration(seconds: 30),     // 设置请求超时时间
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer YOUR_ACCESS_TOKEN', // 替换为你的实际访问令牌
    },
    // 代理设置(如果需要的话)
    proxy: {
      'http': 'http://your-proxy-server:port',
      'https': 'http://your-proxy-server:port',
      'noProxy': 'localhost,127.0.0.1' // 不通过代理的主机或IP
    },
  );

  // 设置全局配置
  ApiAgent.instance.config = config;

  runApp(MyApp());
}

2. 创建一个API服务类

创建一个服务类来封装你的API调用逻辑。

import 'package:api_agent/api_agent.dart';

class ApiService {
  // 获取用户信息的API调用
  Future<Map<String, dynamic>> getUserInfo(String userId) async {
    Map<String, dynamic> params = {
      'user_id': userId,
    };

    try {
      ApiResponse<Map<String, dynamic>> response = await ApiAgent.instance.get(
        '/user/info',
        params: params,
      );

      if (response.isSuccessful) {
        return response.data;
      } else {
        throw Exception('API调用失败: ${response.statusCode} - ${response.message}');
      }
    } catch (e) {
      throw Exception('获取用户信息失败: $e');
    }
  }
}

3. 在UI中使用API服务

在你的Flutter UI中使用上述服务类来调用API并显示数据。

import 'package:flutter/material.dart';
import 'api_service.dart'; // 导入你创建的API服务类

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  String _userInfo = '';
  ApiService _apiService = ApiService();

  @override
  void initState() {
    super.initState();
    _fetchUserInfo('12345'); // 替换为实际的用户ID
  }

  Future<void> _fetchUserInfo(String userId) async {
    try {
      Map<String, dynamic> user = await _apiService.getUserInfo(userId);
      setState(() {
        _userInfo = jsonEncode(user); // 假设你想以JSON格式显示用户信息
      });
    } catch (e) {
      setState(() {
        _userInfo = 'Error: $e';
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter API调用示例'),
        ),
        body: Center(
          child: Text(_userInfo),
        ),
      ),
    );
  }
}

这个示例展示了如何使用 api_agent 插件在Flutter应用中配置代理、封装API调用逻辑,并在UI中显示API调用的结果。请根据你的实际需求调整URL、请求参数和错误处理等细节。

回到顶部