happypass 1.0.4 copy "happypass: ^1.0.4" to clipboard
happypass: ^1.0.4 copied to clipboard

outdated

Http / Https library to speed your development, and feel happy every day!

开始使用 #

当前最新版本为: 1.0.4

在 "pubspec.yaml" 文件中加入

dependencies:
  happypass: ^1.0.4

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

表单数据 - FormDataBody

查看测试代码

我们可以使用 FormDataBody 很便捷地发送表单数据, 如下面的例子:

void main() async {
	// 通过 [Request.construct] 方法直接创建实例
	Request request = Request.construct();
	// 设置 Request 路径
	request.setUrl("xxxxx")
	// 设置 Request 运行环境,放置到 Isolate 中执行
	.addFirstEncoder(const Utf8String2ByteEncoder())
	// 设置解码器
	.addLastDecoder(const Byte2Utf8StringDecoder())
	// 设置拦截器
	.POST(FormDataBody().addPair("hello", "world").addPair("happy", "everyday"));
	// 发送请求并打印响应结果
	print(await request.doRequest());
}

使用 FormDataBody,把要传递的数据以 "键值对" 的方式发过去,就是那么简单。

Multipart 数据 - MultipartDataBody

查看测试代码

如果我们想要上传某个或多个文件,或者一个数据流,可以使用 MultipartDataBody 来实现,例子如下:

void main() async {
	File file = File("xxxx/temp.txt");
	// 通过 [Request.construct] 方法直接创建实例
	Request request = Request.construct();
	// 设置 Request 路径
	request.setUrl("xxx")
	// 设置 Request 运行环境,放置到 Isolate 中执行
	.addFirstEncoder(const Utf8String2ByteEncoder())
	// 设置解码器
	.addLastDecoder(const Byte2Utf8StringDecoder())
	// 设置拦截器
	.POST(MultipartDataBody().addMultipartFile("file", file));
	// 发送请求并打印响应结果
	print(await request.doRequest());
}
0
likes
0
pub points
0%
popularity

Publisher

unverified uploader

Http &#x2F; Https library to speed your development, and feel happy every day!

Repository (GitHub)
View/report issues

License

unknown (LICENSE)

More

Packages that depend on happypass