一个 Flutter 库,用于更轻松地创建 Rest API 客户端。灵感来自 Java Feing。

功能

  • 通过通用接口方便 JSON 编码和解码。
  • 方便的 HTTP 错误处理。
  • 方便的 HTTP 头控制。
  • 动态生成带有路径和查询参数的 URL。
  • HTTP 错误时可配置的重试次数。

入门

只需添加包并遵循上述说明

dependencies:
  hermes_http: ^1.0.0

用法

创建一个客户端类,并在构造函数中提供默认配置和请求模板;

class FruitClient {

  late HermesHttpClient _client;

  FruitClient() {
    //Creates a http client with base url and the common headers
    _client = HermesHttpClient("https://www.fruityvice.com/");
    _client.addHeader("Content-Type", "application/json");
    _client.addHeader("Connection", "keep-alive");
  }
  
}

然后创建请求和响应的数据类,并为每个类提供一个 JsonDecoder、JsonEncoder 接口的实现。

//classes that implements json parser and json encoder, 
//its not mandatory parser and encoder interfaces are implemented by the data class itself. 
//the interface can be implemented by another classes
class Fruit implements JsonDecoder<Fruit>, JsonEncoder<Fruit> {
  String genus = "";
  String name = "";
  int id = 0;
  String family = "";
  String order = "";
  Nutrition nutritions = Nutrition();

  @override
  Fruit fromJson(dynamic jsonMap) {
    Fruit fruit = Fruit();
    fruit.genus = jsonMap['genus'];
    fruit.name = jsonMap['name'];
    fruit.id = jsonMap['id'];
    fruit.family = jsonMap['family'];
    fruit.order = jsonMap['order'];
    fruit.nutritions = NutritionDecoder().fromJson(jsonMap['nutritions']);
    return fruit;
  }

  @override
  Map<String, dynamic> toJson(Fruit obj) {
    
    Map<String, dynamic> map = <String, dynamic>{};

    map['genus'] = obj.genus;
    map['name'] = obj.name;
    map['id'] = obj.id;
    map['family'] = obj.family;
    map['order'] = obj.order;
    map['nutritions'] = NutritionEncoder().toJson(obj.nutritions);

    return map;
  }
}

class Nutrition {
  num carbohydrates = 0;
  num protein = 0;
  num fat = 0;
  num calories = 0;
  num sugar = 0;

}

class NutritionDecoder extends JsonDecoder<Nutrition> {

  @override
  Nutrition fromJson(jsonMap) {
    Nutrition nutrition = Nutrition();
    nutrition.carbohydrates = jsonMap['carbohydrates'];
    return nutrition;
  }
  
}

class NutritionEncoder extends JsonEncoder<Nutrition> {

  @override
  Map<String, dynamic> toJson(Nutrition obj) {
     Map<String, dynamic> map = <String, dynamic>{};
     map['carbohydrates'] = obj.carbohydrates;
     return map;
  }
  
}

使用数据类创建请求模板。
为每个请求模板提供一个 HermesRequest<Request, Response> 引用,请求或响应对象都可以是 void(如果您想忽略 void 响应,只需将其传递给 void)。
然后,在构造函数中实例化请求模板
参数是

  • hermes http 客户端(或 IHermesHttpClient 的自定义实现)
  • http 方法小写
  • 带有方括号内任何参数的路径( /api/fruit/{fruitName} , /api/fruit/nutrition?min={minumunValue}&max={maximumValue} )
  • JsonEncoder 接口的实现(对于 void 值使用 VoidJsonEncoder())
  • JsonDecoder 接口的实现(对于 void 值使用 VoidJsonDecoder())
  • 可选命名参数 maxAttempts 用于配置重试(默认为 3)
  • 可选的自定义请求头( Map<String,String> )

  
class FruitClient {

  late HermesHttpClient _client;

  //declares a request with the request body type and the response body type
  late HermesRequest<void, Fruit> _getFruit;

  late HermesRequest<void, List<Fruit>> _getAllFruit;

  late HermesRequest<Fruit, void> _addFruit;

  FruitClient() {
    //Creates a http client with base url and the common headers
    _client = HermesHttpClient("https://www.fruityvice.com/");
    _client.addHeader("Content-Type", "application/json");
    _client.addHeader("Connection", "keep-alive");

    //Creates the request instance
    _getFruit = HermesRequest(_client, 'get', '/api/fruit/{fruitName}', VoidJsonEncoder(), Fruit());

    //Create the request using the class ListJsonDecoder to parse the json list
    _getAllFruit = HermesRequest(_client, 'get', '/api/fruit/all', VoidJsonEncoder(), ListJsonDecoder<Fruit>(Fruit()));

    //Create the request setting the maxAttemps (retry) to only 1 (defaults 3)
    _addFruit = HermesRequest(_client, 'put', '/api/fruit', Fruit(), VoidJsonDecoder(), maxAttempts: 1);
  }
}
  

然后只需完成客户端方法的公开

class FruitClient {

  late HermesHttpClient _client;

  //declares a request with the request body type and the response body type
  late HermesRequest<void, Fruit> _getFruit;

  late HermesRequest<void, List<Fruit>> _getAllFruit;

  late HermesRequest<Fruit, void> _addFruit;

  FruitClient() {
    //Creates a http client with base url and the common headers
    _client = HermesHttpClient("https://www.fruityvice.com/");
    _client.addHeader("Content-Type", "application/json");
    _client.addHeader("Connection", "keep-alive");

    //Creates the request instance
    _getFruit = HermesRequest(_client, 'get', '/api/fruit/{fruitName}', VoidJsonEncoder(), Fruit());

    //Create the request using the class ListJsonDecoder to parse the json list
    _getAllFruit = HermesRequest(_client, 'get', '/api/fruit/all', VoidJsonEncoder(), ListJsonDecoder<Fruit>(Fruit()));

    //Create the request setting the maxAttemps (retry) to only 1 (defaults 3)
    _addFruit = HermesRequest(_client, 'put', '/api/fruit', Fruit(), VoidJsonDecoder(), maxAttempts: 1);
  }

  //Creates a call to request
  //Pass any kind of param (path, query) in the path as a map 
  Future<Fruit> getFruit(String fruitName) async {
    return await _getFruit.call(pathParams: { 'fruitName': fruitName });
  }

  Future<List<Fruit>> getAllFruit() async {
    _getAllFruit.addHeader("Accept", "application/json"); //dinamically set a header to the request
    return await _getAllFruit.call();
  }

  Future<void> addFruit(Fruit fruit) async {
    await _addFruit.call(body: fruit);
  }

}

完整的示例可以在 github 的 examples 文件夹中找到。

如果 http 调用返回的 http 状态码不是 2xx 系列,将抛出以下异常

class HermesRequestError implements Exception {
  int status = 0;
  String body = "";
  String uri = "";
  String method = "";

  HermesRequestError(this.status, this.method, this.uri, this.body);
}

附加信息

欢迎贡献。

GitHub

查看 Github