Dart SDK中的网络库
在Dart SDK中,网络请求库位于dart:io下,通过HttpClient类进行网络请求。
首先,看下HttpClient的使用例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
post(String url) {
final client = HttpClient();
client.maxConnectionsPerHost = 10;
client.connectionTimeout = Duration(seconds: 5);
client.idleTimeout = Duration(seconds: 10);
client.userAgent = "test user agent";
client.autoUncompress = true;
client.findProxy = (url) => "PROXY 127.0.0.1:1080;DIRECT";
client.postUrl(Uri.parse(url)).then<HttpClientResponse>((request) {
request.headers.forEach((name, values) => print("$name: $values"));
request.headers.add("test-header", "test");
request.write("test");
return request.close();
}).then<String>((response) {
print("get response");
return response.transform(Utf8Decoder()).join("");
}).then((content) {
print(content);
}).catchError((err) {
print("error message: $err");
});
}
post("https://www.baidu.com");
这个例子展示了HttpClient的基本使用方式,以及它的基础功能。
maxConnectionsPerHost
设置单个host的最大连接数。
connectionTimeout
设置连接单个host的超时时间。
idleTimeout
设置连接不活跃时的空闲等待时间。
userAgent
设置所有请求的头User-Agent信息。默认是Dart/<version> (dart:io)。若设置为null, 则不设置的User-Agent。
autoUncompress
设置自动解压缩。只有当response header中的Content-Encoding是gzip时才会去解压缩。
findProxy
设置代理。代理的内容必须符合格式要求,如:
- “DIRECT”
- “PROXY host:post”
- “PROXY host:port; PROXY host2:port2; DIRECT”
创建请求
通过client.openUrl、client.getUrl等方法会创建一个Future<HttpClientRequest>,通过HttpClientRequest可以设置请求头、请求内容等信息。
请求头设置:
1
request.headers.add("test-header", "test");
请求内容:
1
request.write("test");
接收响应内容, 返回Future<HttpClientResponse>:
1
request.close();
解析响应
HttpClientRespinse本质是Stream<List<int>>流,可以通过下面的方式解析成字符串:
1
response.transform(Utf8Decoder()).json("");
第三方网络库Dio
Dio是Dart中的第三方网络库,项目地址:github地址。关于Dio的使用,它里面有详细的介绍。下面只挑选其中的一部分进行阐述。
使用Dio最简单的例子
1
2
3
4
5
6
7
8
9
get() async {
try {
final dio = Dio();
Response<String> response = await dio.get<String>("https://www.baidu.com");
print(response);
} catch(e) {
print(e);
}
}
获取响应流:
1
2
3
4
Response<ResponseBody> rs = await Dio().get<ResponseBody>(url,
options: Options(responseType: ResponseType.stream),
);
print(rs.data.stream);
Dio.request<T>或Dio.get<T>中,T指响应内容的类型。若T设置成String, 则返回类型是ResponseType.plain, 若设置为其他(not dynamic),则返回类型是Response.json。
当然,我们可以直接设置ResponseType,如下面的例子:
1
dio.options.responseType = ResponseType.stream;
在实例化Dio时,也可以传入options:
1
2
3
4
5
6
Options options = new BaseOptions(
baseUrl: "https://www.xx.com/api",
connectTimeout: 5000,
receiveTimeout: 3000,
);
Dio dio = new Dio(options);
文件下载
文件下载的简单例子:
1
2
3
dio.download("/download", "a.png", onReceiveProgress: (count, total) {
//do something
});
第一次参数是下载地址;第二个参数是保存到本地的路径;第三个参数可选,监听下载进度。
拦截器(Interceptor)
Dio支持添加Interceptor, 可作用在发起request之前、响应response之前、抛出异常之前。
设置Interceptor:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
dio.interceptors.add(InterceptorsWrapper(
onRequest: (RequestOptions options) {
print("before request");
return options;
},
onResponse: (Response response) {
print("before response");
return response;
},
onError: (e) {
print("before error");
print(e);
}
));
通过拦截器,可以:
返回假数据
1 2 3 4 5 6
dio.interceptors.add(InterceptorsWrapper( onRequest: (RequestOptions options) { print("before request"); return dio.resolve("返回的假数据"); } ))
通过这种方式返回的数据,不会经过
Interceptor.onResponse。拦截请求
1 2 3 4 5 6
dio.interceptors.add(InterceptorsWrapper( onRequest: (RequestOptions options) { print("before request"); return dio.reject("拦截的原因"); } ))
若拦截了请求,会主动抛出
DioError。同样这种方式的异常不会经过Interceptor.onError。日志
Dio中提供了日志打印的Interceptor.1
dio.interceptors.add(LogInterceptor(responseBody: true));
拦截器的锁
Interceptor支持锁功能。一旦Interceptor锁住之后,后面进来的请求都会等待它解锁之后才会出去。看官方的一个使用场景:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
dio.interceptors.add(InterceptorsWrapper( onRequest: (Options options) { if (csrfToken == null) { //lock the dio. dio.lock(); return tokenDio.get("/token").then((d) { options.headers["csrfToken"] = csrfToken = d.data['data']['token']; }).whenComplete(() => dio.unlock()); // unlock the dio } else { options.headers["csrfToken"] = csrfToken; return options; } } ));
一般的业务场景中,都会要求请求带上
Token。如果未获得Token或Token已过期需要重新刷新时,通过在Interceptor中锁住,暂停所有的请求。同时,异步去获取新的Token,完成后再解锁。丝毫不影响正常的业务逻辑。这种AOP的编程思想,很赞!
变形器(Transformer)
Transformer可以将请求或者响应的参数转换成其他的数据格式。
默认的变形器是DefaultTransformer。当响应格式是json时,DefaultTransformer会自动调用json.decode(responseBody)去解析字符串。问题是,如果json字符串很大时,解析会导致线程阻塞而使得页面卡顿。
可以使用FlutterTransformer替换默认的Transformer。FlutterTransformer的功能很简单,它只是在DefaultTransformer的基础上将json.decode(responseBody)移到了新的isolate中去运算。源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
library dio_flutter_transformer;
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
class FlutterTransformer extends DefaultTransformer {
FlutterTransformer() : super(jsonDecodeCallback: _parseJson);
}
_parseAndDecode(String response) {
return jsonDecode(response);
}
_parseJson(String text) {
return compute(_parseAndDecode, text);
}
使用FlutterTransformer需要格外依赖库: dio_flutter_transformer。
Dio库的引入
依赖配置如下:
1
2
3
dependencies:
dio: ^2.1.2
dio_flutter_transformer: ^2.0.0