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