happypass 1.0.2

  • Readme
  • Changelog
  • Installing
  • new63

开始使用 #

当前最新版本为: 1.0.2

在 "pubspec.yaml" 文件中加入

dependencies:
  happypass: ^1.0.2

github

https://github.com/CimZzz/happypass

构建一个请求 (Request) #

查看测试代码

HappyPass 将请求对象抽象为 Request 类,借由配置 Request 来实现自定义请求的目的。

为了发送请求,首先我们需要构建 Request

// 通过 [Request.construct] 方法直接创建实例
Request request = Request.construct();

然后配置 Request

// 设置 Request 路径
request.setUrl("https://www.baidu.com/")
// 设置 Request 头部
.setRequestHeader("Hello", "World")
// 设置解码器(将响应数据转换为 UTF8 字符串)
.addLastDecoder(const Byte2Utf8StringDecoder())
// 添加拦截器
.addFirstInterceptor(const LogUrlInterceptor())
// GET 请求
.GET();

上述配置了 Request 路径、头部、解码器、拦截器和请求方法。

然后发送请求获得结果

// 发送请求并打印响应结果
print(await request.doRequest());

以上完成了一次简单的 GET 请求。

编码器 #

查看测试代码

POST 请求中,最终发送的请求数据是 List<int> 类型,而编码器的作用就是将 body 中的数据进行转换,最终转化为 List<int> 类型数据。

如下:

// 通过 [Request.construct] 方法直接创建实例
Request request = Request.construct();
// 设置 Request 路径
request.setUrl("http://xxxxxx")
// 设置请求方法为 POST
// body 是一个 Map,所以需要配置编码器将 Map 转化为 List<int> 数据
// 假设服务端需要的数据时 JSON 字符串
.POST({
	"data": "helloworld"
})
// 首先将 Map 转化为 JSON 字符串
.addFirstEncoder(const JSON2Utf8StringEncoder())
// 然后将 String 转化为 List<int>
.addFirstEncoder(const Utf8String2ByteEncoder())
// 将响应数据 List<int> 转化为 String
.addLastDecoder(const Byte2Utf8StringDecoder());
// 发送请求并打印响应结果
print(await request.doRequest());

这样,编码器先通过 JSON2Utf8StringEncoderMap 转换为 JSON 字符串,再通过 Utf8String2ByteEncoderJSON 字符串转换为 List<int> 字节数组。

目前只有 POST 请求会用到编码器。

解码器 #

查看测试代码

在请求之后,接收到的响应数据是 List<int> 类型,我们可以通过配置解码器的方式将其转换为我们所需要的类型。

使用编码器实例中的部分代码,针对解码器部分进行修改


// 通过 [Request.construct] 方法直接创建实例
Request request = Request.construct();
// 设置 Request 路径
request.setUrl("http://xxxxxx")
...
// 将响应数据 List<int> 转化为 String
.addLastDecoder(const Byte2Utf8StringDecoder())
// 然后 String 转化为 Map
.addLastDecoder(const Utf8String2JSONDecoder());
// 发送请求并打印响应结果
print(await request.doRequest());

解码器先通过 Byte2Utf8StringDecoderList<int> 转换为 JSON 字符串,在通过 Utf8String2ByteEncoderJSON 字符串转换为 Map

拦截器 #

查看测试代码

拦截器负责处理 Request 和生成 Response。默认情况下,每个请求都会携带一个缺省的拦截器 BusinessPassInterceptor,该拦截器主要的目的就是将请求 转化为对应的 Response

首先声明两个拦截器:

class SimpleIntercept1 extends PassInterceptor {
	const SimpleIntercept1(this.name);

	final String name;

	@override
	Future<PassResponse> intercept(PassInterceptorChain chain) {
		print(name);
		return chain.waitResponse();
	}
}

class SimpleIntercept2 extends PassInterceptor {
	const SimpleIntercept2(this.name);

	final String name;

	@override
	Future<PassResponse> intercept(PassInterceptorChain chain) {
		print(name);
		return chain.requestForPassResponse();
	}
}

可以给请求配置拦截器观察一下执行流程:

// 通过 [Request.construct] 方法直接创建实例
Request request = Request.construct();
// 设置 Request 路径
request.setUrl("https://www.baidu.com/")
// 设置 Request 头部
.setRequestHeader("Hello", "World")
// 设置解码器
.addLastDecoder(const Byte2Utf8StringDecoder())
// 添加拦截器
.addFirstInterceptor(const SimpleIntercept1("Chain A"))
.addFirstInterceptor(const SimpleIntercept1("Chain B"))
.addFirstInterceptor(const SimpleIntercept1("Chain C"))
.addFirstInterceptor(const SimpleIntercept1("Chain D"))
.addFirstInterceptor(const SimpleIntercept1("Chain E"))
// GET 请求
.GET();
// 发送请求并打印响应结果
print(await request.doRequest());

执行结果如下:

chain E
chain D
chain C
chain B
chain A
...
// real response data

拦截器采取的方式是首位插入,所以最先添加的拦截器最后执行

正常情况下,拦截器的工作应该如下

pass request : E -> D -> C -> B -> A -> BusinessPassInterceptor

return response : BusinessPassInterceptor -> A -> B -> C -> D -> E

上述完成了一次拦截工作,Request 的处理和 Response 的构建都在 BusinessPassInterceptor 这个拦截器中完成

如果在特殊情况下,某个拦截器(假设 B)意图自己完成请求处理,那么整个流程如下:

pass request : E -> D -> C -> B

return response : B -> C -> D -> E

上述在 B 的位置直接拦截,请求并未传递到 BusinessPassInterceptor,所以 Request 的处理和 Response 的构建都应由 B 完成

这次我们在 B 点进行拦截

// 通过 [Request.construct] 方法直接创建实例
Request request = Request.construct();

// 设置 Request 路径
request.setUrl("https://www.baidu.com/")
// 设置解码器
.addLastDecoder(const Byte2Utf8StringDecoder())
// 添加拦截器
.addFirstInterceptor(const SimpleIntercept1("Chain A"))
.addFirstInterceptor(const SimpleIntercept2("Chain B"))
.addFirstInterceptor(const SimpleIntercept1("Chain C"))
.addFirstInterceptor(const SimpleIntercept1("Chain D"))
.addFirstInterceptor(const SimpleIntercept1("Chain E"))
// GET 请求
.GET();

// 发送请求并打印响应结果
print(await request.doRequest());

执行结果如下

chain E
chain D
chain C
chain B
...
// real response data

拦截器回调参数中的 PassInterceptorChain 提供了一些便捷的方法:

class PassInterceptorChain {

    ...

    /// 等待其他拦截器返回 `Response`
    Future<PassResponse> waitResponse() async
    
    /// 获取拦截链请求修改器
    /// 可以在拦截器中修改请求的大部分参数,直到有 `PassResponse` 返回
    ChainRequestModifier get modifier;
    
    /// 实际执行 `Request` 获得 `Response`
    /// 提供了一些可选回调,最大限度满足自定义 Request 的自由
    Future<PassResponse> requestForPassResponse async ({
        /// HttpClient 构造器
        /// 可以自定义 HttpClient 的构造方式
        HttpClient httpClientBuilder(),
        /// HttpClientRequest 构造器
        /// 可以自定义 HttpClientRequest 的构造方式
        Future<HttpClientRequest> httpReqBuilder(HttpClient client, ChainRequestModifier modifier),
        /// HttpClientRequest 消息配置构造
        /// 用于配置请求头,发送请求 Body
        /// 如果该方法返回了 PassResponse,那么该结果将会直接被当做最终结果返回
        PassResponse httpReqInfoBuilder(HttpClientRequest httpReq, ChainRequestModifier modifier),
        /// HttpClientResponse 构造器
        /// 可以自定义 HttpClientResponse 的构造方式
        Future<HttpClientResponse> httpRespBuilder(HttpClientRequest httpReq),
        /// Response Body 构造器
        /// 可以自行读取响应数据并对其修改,视为最终返回数据
        Future<PassResponse> responseBuilder(HttpClientRequest httpReq, ChainRequestModifier modifier)
    });

    ...
}

具体的细节逻辑可以参考源代码

请求原型 #

查看测试代码

避免大量不必要的请求配置操作,可以使用请求原型来实现快速构建配置相同的请求

// 通过 [Request.construct] 方法直接创建实例
RequestPrototype requestPrototype = RequestPrototype();

// 设置 Request 路径
requestPrototype.setUrl("https://www.baidu.com/")
// 设置 Request 头部
.setRequestHeader("Hello", "World")
// 设置解码器
.addLastDecoder(const Byte2Utf8StringDecoder())
// 添加拦截器
.addFirstInterceptor(const LogUrlInterceptor());
// 不允许原型配置请求方法
//.GET();

// 由原型孵化出 Request
final request1 = requestPrototype.spawn();
final request2 = requestPrototype.spawn();
final request3 = requestPrototype.spawn();
// 异步执行所有请求
request1.GET().doRequest();
request2.GET().doRequest();
request3.GET().doRequest();
// 发送请求并打印响应结果
print("request1 : ${await request1.doRequest()}");
print("request2 : ${await request2.doRequest()}");
print("request3 : ${await request3.doRequest()}");

需要注意的是,为了避免 RequestPrototype 持有大量 body 而导致的内存问题,所以禁止 Prototype 配置请求方法。

请求运行环境代理 #

查看测试代码

Request 默认会在当前 Isolate 下执行请求,而一些比如 FlutterIsolate 通常会做一些 UI 渲染相关工作,大量的请求很可能会 导致其 UI 卡顿。因此,我们可以配置 Request 运行环境代理,将请求某些操作(如对请求 Body 进行编码)放到其他 Isolate 中执行,以此达到优化的目的

比如我们配置一个 Isolate 请求代理

class _Receiver<T, Q> {
	_Receiver(this.message, this.callback, this.port);
	final T message;
	final AsyncRunProxyCallback<T, Q> callback;
	final SendPort port;
	
	Future<Q> execute() async {
		return await callback(message);
	}
}


void _doProxy(_Receiver receiver) async {
	try {
		final result = await receiver.execute();
		receiver.port.send(result);
	}
	catch(e) {
		receiver.port.send(ErrorPassResponse());
	}
}

void main() async {
	// 通过 [Request.construct] 方法直接创建实例
	Request request = Request.construct();
	// 设置 Request 路径
	request.setUrl("https://www.baidu.com/")
	// 设置 Request 运行环境,放置到 Isolate 中执行
	.setRequestRunProxy(<T, Q>(asyncCallback, message) async {
		final receiverPort = ReceivePort();
		final isolate = await Isolate.spawn(_doProxy, _Receiver(message, asyncCallback, receiverPort.sendPort));
		final result = await receiverPort.first;
		receiverPort.close();
		isolate.kill();
		if(result is ErrorPassResponse) {
			throw IOException;
		}
		return result;
	})
	// 设置解码器
	.addLastDecoder(const Byte2Utf8StringDecoder())
	// 设置拦截器
	.addFirstInterceptor(const LogUrlInterceptor())
	// GET 请求
	.GET();
	// 发送请求并打印响应结果
	print(await request.doRequest());
}

1.0.0 #

  • 上传至 pub 库
  • 支持 RequestPrototype(请求原型),可以通过该模板快速生成请求
  • 支持使用拦截器
  • 支持配置自定义请求 body 编码器
  • 支持配置自定义响应数据解码器

1.0.1 #

  • 修复请求执行代理 BUG

1.0.2 #

  • 新增操作 Url 的方法,现在可以追加路径和参数了

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  happypass: ^1.0.2

2. Install it

You can install packages from the command line:

with pub:


$ pub get

with Flutter:


$ flutter pub get

Alternatively, your editor might support pub get or flutter pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:happypass/happypass.dart';
  
Popularity:
Describes how popular the package is relative to other packages. [more]
26
Health:
Code health derived from static analysis. [more]
99
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
100
Overall:
Weighted score of the above. [more]
63
Learn more about scoring.

We analyzed this package on Dec 2, 2019, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.6.1
  • pana: 0.12.21

Platforms

Detected platforms: Flutter, other

Primary library: package:happypass/happypass.dart with components: io.

Health suggestions

Fix lib/src/http_interceptors.dart. (-0.50 points)

Analysis of lib/src/http_interceptors.dart reported 1 hint:

line 151 col 25: Avoid empty catch blocks.

Fix lib/src/http_mixins.dart. (-0.50 points)

Analysis of lib/src/http_mixins.dart reported 1 hint:

line 396 col 11: Don't explicitly initialize variables to null.

Format lib/happypass.dart.

Run dartfmt to format lib/happypass.dart.

Fix additional 4 files with analysis or formatting issues.

Additional issues in the following files:

  • lib/src/http.dart (Run dartfmt to format lib/src/http.dart.)
  • lib/src/http_decoders.dart (Run dartfmt to format lib/src/http_decoders.dart.)
  • lib/src/http_encoders.dart (Run dartfmt to format lib/src/http_encoders.dart.)
  • lib/src/http_responses.dart (Run dartfmt to format lib/src/http_responses.dart.)

Maintenance suggestions

Maintain an example.

None of the files in the package's example/ directory matches known example patterns.

Common filename patterns include main.dart, example.dart, and happypass.dart. Packages with multiple examples should provide example/README.md.

For more information see the pub package layout conventions.

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.2.0 <3.0.0