happypass-Flutter/Dart 高效、高度自由化 HTTP 請求框架

happypass

happy pass,pass happy to everybody!

happypass 是一個高度自由化、可定製的 http 請求庫,如果你喜歡掌控自己的代碼,那麼一定會愛上它!

本項目是開源項目,如果大家有好的想法和意見,可以告知我或者一起參與其中,共同維護我們的開源環境。

快速集成

當前最新版本爲: 1.0.7

在 “pubspec.yaml” 文件中加入

dependencies:
  happypass: ^1.0.7

github

https://github.com/CimZzz/happypass

最詳細的示例

happypass 字面意思就是想要 pass happy to everybody,對於提高工程師使用體驗更是視爲重中之重。所以在 happypass 中,有着大量詳細示例以供參考,幫助我們工程師
能夠快速上手使用。

示例目錄

構建一個請求 (Request)

happypass 將請求對象抽象爲 Request 類,藉由配置 Request 來實現自定義請求的目的。

下面是一個極簡的示例:

import 'package:happypass/happypass.dart';
void main() async {
	PassResultResponse result = await Request.quickGet(url: "https://www.baidu.com/", configCallback: (request) {
		request.stringChannel();
	});

	print(result);
}

僅僅幾行代碼,你就完成了一次 GET 請求!

當然,這是最基本的一小部分功能,還有非常多的強大功能幫助你實現主宰自己的 http 請求。

如果想要全面瞭解 happypass 功能覆蓋,還請查看詳細示例

對於上面的示例,我們可以做一些擴展配置,如設置請求頭部等:

request.setRequestHeader("content-type", "application/json");

需要注意的是,Request 無法直接實例化。如果想要構建一個全新的 Request 對象,請使用 Request.construct() 方法

想了解 happypass 當前版本全部的配置,點擊查看

RequestPrototype - 請求原型

RequestPrototype(請求原型),也可以理解爲請求的模板。利用請求原型預先配置好某些屬性,然後在使用的時候快速生成一個配置好的請求,這樣做的好處是避免重複配置請求參數,防止不必要的代碼冗餘。

實例化一個請求原型

import 'package:happypass/happypass.dart';
void main() async {
    RequestPrototype prototype = RequestPrototype();
}

像請求一樣,我們可以爲其配置一個基於 utf8 字符串的編解碼器

prototype.stringChannel();

量化生成並執行請求

// 快速孵化請求,量化執行
for(int i = 0 ; i < 10 ; i ++) {
    print(await prototype.spawn().GET().doRequest());
}

從上面小例子可以大致地瞭解請求原型的作用 ———— 模板

瞭解更多 RequestPrototype 配置,點擊查看 點擊查看

快速請求方法

可能在某些情況下,我們想要簡化代碼複雜度,優化可讀性,可以使用 happypass 提供的快速請求方法:

  • quickGet: 快速 GET 請求
  • quickPost: 快速 POST 請求

這兩個方法都可以設置請求原型的方式來孵化請求

Request.quickGet(
    url: "xxx",
    prototype: prototype,
);

Request.quickPost(
    url: "xxx",
    body: xxx,
    prototype: prototype,
);

關於這兩個方法更爲細緻地介紹,請參考樣例,點擊查看

HTTP 攔截器

happypass 提供了強大的攔截器功能,在這裏你可以對自己的請求進行高度自由化的定製!首先讓我們瞭解一下攔截器的工作原理吧!

攔截器是整個 happypass 的核心,每個請求缺省都會帶有一個 BusinessPassInterceptor 攔截器。該攔截器的作用就是執行實際的請求邏輯。

攔截器的工作原理可以簡單描述爲一條請求鏈路,最終的目的是獲取響應結果。

這裏指的響應結果爲 ResultPassResponse 的子類。該類是 happypass 定義的響應結果類
該類有兩個子類,分別表示請求的成功與失敗:

  • ErrorPassResponse: 表示請求失敗
  • SuccessPassResponse: 表示請求成功

正常情況下,攔截器的工作應該如下

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 完成
需要注意的是,如果攔截器只是對 Request 進行修改或者觀察,並不想實際處理的話,請調用
PassInterceptorChain.waitResponse 方法,表示將 Request 向下傳遞,然後將其結果返回表示將 Response 向上返回。

需要注意的是,如果攔截器不想對請求進行攔截,請務必調用 PassInterceptorChain.waitResponse 方法並且其結果返回(或者根據其結果進行二次加工後的結果)

我們也可以添加攔截器在請求鏈路上做一些自定義行爲,比如攔截來自某個域名的請求

// 爲了方便演示,我們採用 [SimplePassInterceptor] 類,只需傳遞迴調閉包即可實現攔截的功能
final interceptor = SimplePassInterceptor((chain) async {
	final httpUrl = HttpUtils.resolveUrl(chain.modifier.getUrl());
	if(httpUrl != null && httpUrl.host == "www.baidu.com") {
		return ErrorPassResponse(msg: "block www.baidu.com request");
	}

	return chain.waitResponse();
});

上面例子完成了 block 全部來自 www.baidu.com 域名下的請求。當然,不要忘了將它添加到你的請求配置中,否則這一切都白做了!

Request.get(url: "https://www.baidu.com", configCallback: (request) {
    request.addFirstInterceptor(interceptor);
})

該請求最終的響應結果打印出來爲:

block www.baidu.com request

相信這個小例子已經讓大家對攔截器有了一個初步的瞭解

更多攔截器的使用方法與詳細探究,請參考實例,點擊查看

請求 Body、編碼器、解碼器

happypass 中,一次完整請求數據流程大致如下:

  1. 請求 Body 經過編碼器編碼爲 List<int>byte 數據(GET 請求會跳過該步驟)
  2. byte 數據發送,獲得響應的 byte 數據
  3. 將響應的 byte 經過解碼器解碼爲指定的數據結構返回

想必編碼器與解碼器並不陌生,happypass 也提供了一些默認的編解碼器。

happypass 提供的編碼器有:

  • GZip2ByteEncoder: GZIP 編碼器。轉換模式爲: List -> List(byte 轉 byte)
  • Utf8String2ByteEncoder: utf8 字符串編碼器。轉換模式爲: String -> List(字符串轉 utf8 格式的 byte 數據)
  • JSON2Utf8StringEncoder: JSON 編碼器。轉換模式爲: Map -> String(Map 轉字符串)

happypass 提供的解碼器有:

  • Byte2GZipDecoder: GZIP 解碼器。轉換模式爲: List -> List(byte 轉 byte)
  • Byte2Utf8StringDecoder: utf8 字符串解碼器。轉換模式爲: List -> String(utf8 格式的 byte 數據轉字符串)
  • Utf8String2JSONDecoder: JSON 解碼器。轉換模式爲: String -> Map(字符串轉 Map)

如果以上編碼器或者解碼器不能滿足你的需要,可以定義一個繼承自 HttpMessageEncoderHttpMessageDecoder 繼承實現自定義的編解碼器。

在需要發送流數據的請求中(比如 POST 請求),必須傳遞一個 body 作爲請求 body:

body 參數有兩種選擇:

  1. 某種類型數據。該類型數據會經過編碼器層層編碼,最終轉換爲 List<int> 類型的 byte 數據(如果通過編碼器轉換的最終數據不爲 List<int>,則會拋出異常中斷請求)
  2. RequestBody 子類

RequestBody 子類會按照一定的規則提供請求數據,具體可以參考相關示例。

下面列舉一下 happypass 提供的 RequestBody

  • FormDataBody: 表單鍵值對請求數據,如 “key1=value1&key2=value2” 這種標準表單結構
  • MultipartDataBody: Multipart 表單請求數據,可以傳遞文件與流數據
  • StreamDataBody: 流數據請求數據,直接讀取流中數據作爲請求數據

如果以上請求體數據不能滿足你的需求,那麼去定義一個繼承自 RequestBody 的類作爲屬於你自己的自定義 RequestBody 吧!

請求中斷

happypass 允許開發者隨時隨地中斷已經發生的或者尚未發生的請求。

使用方法也很簡單:

void main() async {
    // 首先我們需要實例化一個 RequestCloser 對象
    final requestCloser = RequestCloser();
    // 發送 GET 請求,並使用攔截器,在執行完成請求後中斷
    final result = await Request.quickGet(url: "https://www.baidu.com", configCallback: (request) {
        // 配置請求中斷器
        request.addRequestCloser(requestCloser);
    });
}

按照上面方法你就成功配置了一個請求中斷器。中斷方法也很簡單

requestCloser.close();

這樣即可中斷請求,無論當前請求處於何種狀態都可以調用此方法(正在執行或者尚未執行,甚至還沒有配置該中斷器之前)

通常來說,中斷都會返回一個 ErrorPassResponse,但是在特定情況下,也可以中斷請求立即返回一個指定的響應結果:

requestCloser.close(finishResponse: /* a response derived from `ResultPassResponse`*/);

這樣就能由你指定一個任意的請求響應結果,哪怕與該請求期望的響應結果完全無關。

或者在極端情況下,當一箇中斷器應用到多個請求,在中斷的時候,需要根據每個請求返回各自不同的響應結果,可以在構建中斷器的時候那麼做:

RequestCloser(responseChooseCallback: (ChainRequestModifier modifier) {
    return ErrorPassResponse();
});

配置一個 RequestCloserResponseChooseCallback,在中斷請求時,每個請求都會觸發該回調返回對應的響應結果。
結合請求 id 使用可以達到最大效果。

如果該回調返回 null,那麼依舊會採用 close 中指定的響應結果

靈活地運用請求中斷器,可以使工程師們的編碼效率事半功倍。

瞭解更多請求中斷器運用的方法,請參考示例,點擊查看

請求代理

happypass 提供了十分便捷的代理設置方式,使用以下方法即可快速設置代理:

request.addHttpProxy("localhost", 8888);

請求將會嘗試使用 localhost:8888 進行代理,如果代理無法生效,仍然會從本地發起請求。

超時設置

一般我們對於請求時長都有嚴格要求,如果超時需要中斷當前請求連接並返回異常。按照下面方法,可以非常簡單的配置超時時間


// 設置總超時時間
// 總時長超過超時時間將會拋出異常
// * 攔截器處理時間也算在總時長之內
request.setTotalTimeOut(const Duration(seconds: 5));

// 設置連接超時時間
// 連接時長超過超時時間將會拋出異常
request.setConnectTimeOut(const Duration(seconds: 5));

// 設置讀取超時時間
// 讀取時長超過超時時間將會拋出異常
request.setReadTimeOut(const Duration(seconds: 5));

happypass 也提供了相當詳細的示例文檔,點擊查看

請求運行代理

這個概念容易和 http 代理混淆,兩者的區別如下:

請求運行代理: 在請求配置與解析時,可能會發生一些比較耗時的操作(例如解析 JSON),官方建議使用 Isolate 來單獨處理這些操作,以防止造成
卡頓等情況,而請求運行代理就是爲了解決這個問題而生的。你可以在請求代理中使用一個 Isolate 去執行參數中的回調,然後將其結果返回。

請求 HTTP 代理: 使用指定的 HTTP 代理服務器發送請求。

Flutter 中,compute 方法與請求運行代理高度契合,可以很方便配置

setRequestRunProxy(<T, Q>(asyncCallback, message) async {
  return await compute(asyncCallback, message);
});

這樣,複雜的編解碼操作都交給另外的 Isolate 處理了。

示例中也介紹了 Dart 的實現方式,點擊查看

設置 Cookie 管理器

happypass 可以配置 CookieManager,幫助開發者管理請求中的 Cookie

request.setCookieManager(MemoryCacheCookieManager());

MemoryCacheCookieManagerhappypass 提供的一個內存 Cookie 緩存管理器,開發者可以自定義 CookieManager 將之替換。

  • 默認情況下,請求是不會攜帶 CookieManger

聯繫我

如果您在使用的過程中有更好的想法,或者發現什麼問題,都可以聯繫我,共同進行交流:

QQ: 1152564696
Mail: [email protected]

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章