Home Dart中网络请求
Post
Cancel

Dart中网络请求

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-Encodinggzip时才会去解压缩。

findProxy

设置代理。代理的内容必须符合格式要求,如:

  • “DIRECT”
  • “PROXY host:post”
  • “PROXY host:port; PROXY host2:port2; DIRECT”

创建请求

通过client.openUrlclient.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。如果未获得TokenToken已过期需要重新刷新时,通过在Interceptor中锁住,暂停所有的请求。同时,异步去获取新的Token,完成后再解锁。丝毫不影响正常的业务逻辑。这种AOP的编程思想,很赞!

变形器(Transformer)

Transformer可以将请求或者响应的参数转换成其他的数据格式。

默认的变形器是DefaultTransformer。当响应格式是json时,DefaultTransformer会自动调用json.decode(responseBody)去解析字符串。问题是,如果json字符串很大时,解析会导致线程阻塞而使得页面卡顿。

可以使用FlutterTransformer替换默认的TransformerFlutterTransformer的功能很简单,它只是在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
This post is licensed under CC BY 4.0 by the author.