Flutter HTTP上传文件使用详解
Flutter HTTP 上传文件详解
Flutter HTTP上传文件详解
最近使用Flutter开发新App,需要使用Http上传文件,对Flutter中Http上传进行一些总结
multipart/form-data
一个 HTML 表单中的 enctype 有三种类型
- application/x-www-urlencoded
- multipart/form-data
- text-plain
默认情况下是 application/x-www-urlencoded,当表单使用 POST 请求时,数据会被以 x-www-urlencoded方式编码到 Body 中来传送。
如果要发送大量的二进制数据(non-ASCII),application/x-www-form-urlencoded显然是低效的,因为它需要用 3 个字符来表示一个 non-ASCII 的字符。因此,这种情况下,应该使用multipart/form-data格式。
我们需求中正是需要使用multipart/form-data格式来上传文件。
multipart/form-data请求的内容格式如下:
POST http://www.example.com HTTP/1.1 Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryyb1zYhTI38xpQxBK ------WebKitFormBoundaryyb1zYhTI38xpQxBK Content-Disposition: form-data; name="city_id" 1 ------WebKitFormBoundaryyb1zYhTI38xpQxBK Content-Disposition: form-data; name="company_id" 2 ------WebKitFormBoundaryyb1zYhTI38xpQxBK Content-Disposition: form-data; name="file"; filename="chrome.png" Content-Type: image/png PNG ... content of chrome.png ... ------WebKitFormBoundaryyb1zYhTI38xpQxBK--
每个请求的body中可以包含多个字段,如上面的请求中就包含“city_id”、“company_id”、“file”三个字段,前两个字段的值是字符串,“chrome.png”则是上传的文件,文件以二进制数组转换成字符串来传递,如果文件较大,会分为多个POST请求传递给服务器,前面的POST请求会设置为keep-alive,最后一个POST请求才close。
“------WebKitFormBoundaryyb1zYhTI38xpQxBK”是分隔符(boundary),用于分割body中的每个字段,boundary可以自定义,在header中的boundary字段说明,在body中会以如下格式添加进去:
... Content-Type: multipart/form-data; boundary=${boundary} --${boundary} ... ... --${boundary}--
Flutter Http插件
Flutter中Http请求方式可以直接使用Dart:io中的HttpClient,但是目前不支持multipart/form-data格式,为了方便,我们使用了dart官方封装的http插件。
官方文档中介绍了一些简单的使用方法,这里就不再赘述,比较蛋疼的是官方文档中只介绍了一些基础功能的用法,像multipart/form-data的用法都没有介绍。这里主要是介绍一下multipart/form-data的用法。
由于我们的需求是一定要使用multipart/form-data,一开始纠结了好久找不到在Flutter中该如何写代码;网上有人介绍可以使用dio这个库来使用multipart/form-data格式上传文件,但是我们的项目里已经引入了http插件,不想再换成dio,所以还是想最好能够用http插件实现multipart上传功能。
MultipartRequest使用
经过一段时间搜索,终于在http的Github的Issue里找到了一些线索,有人提问关于multipart的问题,我这才发现在http的包里有一个MultipartRequest的类,这个类正是对multipart/form-data格式的实现。
这个类的使用在源码的注释里有简单的说明,具体用法如下:
var uri = Uri.parse("http://pub.dartlang.org/packages/create"); var request = new http.MultipartRequest("POST", uri); request.fields['user'] = 'nweiz@google.com'; request.files.add(new http.MultipartFile.fromPath( 'package', 'build/package.tar.gz', contentType: new MediaType('application', 'x-tar')); var response = await request.send(); if (response.statusCode == 200) print('Uploaded!');
在MultipartRequest中,fields是一个Map;files是一个MultipartFile的List:
/// The form fields to send for this request. final Map<String, String> fields; /// The private version of [files]. final List<MultipartFile> _files; /// The list of files to upload for this request. List<MultipartFile> get files => _files;
fields里存储的key-value就是body中的文本字段,key是name,value是内容。files里存储的就是需要上传的文件,MultipartFile有两个命名构造方法和一个静态方法:
/// Creates a new [MultipartFile] from a byte array. /// /// [contentType] currently defaults to `application/octet-stream`, but in the /// future may be inferred from [filename]. factory MultipartFile.fromBytes(String field, List<int> value, {String filename, MediaType contentType}) /// Creates a new [MultipartFile] from a string. /// /// The encoding to use when translating [value] into bytes is taken from /// [contentType] if it has a charset set. Otherwise, it defaults to UTF-8. /// [contentType] currently defaults to `text/plain; charset=utf-8`, but in /// the future may be inferred from [filename]. factory MultipartFile.fromString(String field, String value, {String filename, MediaType contentType}) // TODO(nweiz): Infer the content-type from the filename. /// Creates a new [MultipartFile] from a path to a file on disk. /// /// [filename] defaults to the basename of [filePath]. [contentType] currently /// defaults to `application/octet-stream`, but in the future may be inferred /// from [filename]. /// /// Throws an [UnsupportedError] if `dart:io` isn't supported in this /// environment. static Future<MultipartFile> fromPath(String field, String filePath, {String filename, MediaType contentType})
最后调用MultipartRequest中的send方法会将fields和files中的内容按照格式生成body,然后发送POST请求。
需要注意的是request.send()返回的是StreamedResponse,和普通的Response还不一样,需要用如下方法才能读取内容:
var respStr = await response.stream.transform(utf8.decoder).join(); LogUtil.i("upload response is $respStr");
Flutter请求抓包问题
一般做HTTP请求都会想要抓包来看一下请求的格式和内容对不对,但是这次连上代理以后发现其他请求都能抓到,只有Flutter里的请求抓不到…
上网搜索了一下,发现已经有人遇到过这个问题,并给出了解决方案,具体的分析这里就不再贴了,详情请看Flutter中http请求抓包解决方案。这里只写一下结论,增加如下代码就可以抓包了,"http_proxy"填代理PC的IP和端口即可。
var httpClient = new HttpClient(); httpClient.findProxy = (url) { return HttpClient.findProxyFromEnvironment(url, environment: {"http_proxy": 'http://192.168.124.7:8888',}); };
但是有一个问题,我们用的是http插件,不是原生的HttpClient,这又应该怎么设置呢?
遇事不决读源码,在http中的client.dart里,我看到这样的注释:
/// Creates a new client. /// /// Currently this will create an `IOClient` if `dart:io` is available and /// a `BrowserClient` if `dart:html` is available, otherwise it will throw /// an unsupported error.
意思是如果有dart:io,就会创建一个IOClient;如果有dart:html,就会创建一个BrowserClient。HttpClient正是dart:io中的一员,所以我们来看看IOClient的实现:
/// The underlying `dart:io` HTTP client. HttpClient _inner; /// Creates a new HTTP client. IOClient([HttpClient inner]) : _inner = inner ?? new HttpClient();
这就很清晰了,IOClient实际就是HttpClient的封装,那我们只要自己创建一个HttpClient设置好代理后再创建IOClient就可以了,所以我们完整的上传代码就是:
static Future<bool> upload(BaseUploadData data) async { var request = await data.getRequest(); HttpClient httpClient = new HttpClient(); httpClient.findProxy = (url) { return HttpClient.findProxyFromEnvironment(url, environment: {"http_proxy": 'http://10.45.109.70:8088',}); }; IOClient client = IOClient(httpClient); var response = await client.send(request); var respStr = await response.stream.transform(utf8.decoder).join(); LogUtil.i("upload response is $respStr");return response.statusCode == 200; }
- 点赞
- 收藏
- 分享
- 文章举报
- httpClient使用详解和上传文件到指定的http地址
- HttpClient使用详解(MultipartEntityBuilder 上传文件等)
- HttpClient使用详解(http伪造文件上传请求)
- Swift - HTTP网络操作库Alamofire使用详解2(文件上传)
- HttpClient使用详解 (如何上传文件到文件服务器)
- Flex与.NET互操作(五):使用FileReference+HttpHandler实现文件上传/下载
- 截获asp.net上传文件过大IIS报错的Httpmodule代码 --方便以后使用
- Android中使用HTTP服务上传文件
- 如何使用HttpUnit进行上传文件测试
- C#在WinForm下使用HttpWebRequest上传文件并显示进度
- 理解HTTP消息头 (五)——使用multipart/form-data上传文件
- C#在WinForm下使用HttpWebRequest上传文件并显示进度
- C#在WinForm下使用HttpWebRequest上传文件并显示进度
- 如何在Symbian中使用Http上传大文件
- C#在WinForm下使用HttpWebRequest上传文件并显示进度
- 使用HttpURLConnection上传文件,进度条显示不正确
- 使用HTTP/HTTPS向服务器上传、下载文件
- 使用WinHttp接口实现HTTP协议Get、Post和文件上传功能
- Java使用HttpURLConnection上传文件
- C#在WinForm下使用HttpWebRequest上传文件并显示进度