流程图
整个Fetch过程依赖的是三个组件的共同作用,
Interceptors,包括RequestInterceptors, ResponseInterceptor,ErrorInterceptor实现整个逻辑过程的流转和控制。参考Dio Interceptor分析
HttpClientAdapter,实现底层socket的建立和数据发送接收流程,主要涉及openurl, close, request stream写入和response stream读取的过程.参考Dio clientAdapter分析
Transformer,实现同步数据转换逻辑,实现请求数据转换和stream处理,响应数据转换和stream处理,参考Dio Transform分析
上传数据流
Dio上传逻辑主要是通过RequestOption.data通过FormData结构来实现,
FormData用于创建multipart/form-data数据写入流,用于向http服务器提供forms和file.主要参数是fields和files.这里引入了MultipartFile,用于实现文件分块传递。
/// A class to create readable "multipart/form-data" streams.
/// It can be used to submit forms and file uploads to http server.
class FormData {
/// The form fields to send for this request.
final fields = <MapEntry<String, String>>[];
/// The [files].
final files = <MapEntry<String, MultipartFile>>[];
}
FormData的构造器主要是fromMap
FormData.fromMap(
Map<String, dynamic> map, [
ListFormat collectionFormat = ListFormat.multi,
this.camelCaseContentDisposition = false,
])
实际应用例子
final formData = FormData.fromMap({
'age': 25,
'file': await MultipartFile.fromFile(
'./example/upload.txt',
filename: 'upload.txt',
)
});
// Send FormData
response = await dio.post('/test', data: formData);
print(response);
FormData类主要实现http协议传输的约定,
对传入的map数据编码
在请求头部添加Content-Disposition
如果是文件传输,在请求头部添加filename,根据file.contentype对contenttype进行变更,根据file.headers对请求头部添加相应字段
添加–$boundary\r\n实现数据块分界。
FormData.finalize实现stream的生成(fields和files),提供给dio.transformdata实现请求body数据写入逻辑。
Stream<List<int>> finalize() {
if (isFinalized) {
throw StateError(
'The FormData has already been finalized. '
'This typically means you are using '
'the same FormData in repeated requests.',
);
}
_isFinalized = true;
final controller = StreamController<List<int>>(sync: false);
void writeAscii(String string) {
controller.add(utf8.encode(string));
}
void writeUtf8(String string) => controller.add(utf8.encode(string));
void writeLine() => controller.add([13, 10]); // \r\n
for (final entry in fields) {
writeAscii('--$boundary\r\n');
writeAscii(_headerForField(entry.key, entry.value));
writeUtf8(entry.value);
writeLine();
}
Future.forEach<MapEntry<String, MultipartFile>>(files, (file) {
writeAscii('--$boundary\r\n');
writeAscii(_headerForFile(file));
return writeStreamToSink(
file.value.finalize(),
controller,
).then((_) => writeLine());
}).then((_) {
writeAscii('--$boundary--\r\n');
controller.close();
});
return controller.stream;
}