简介
Http网络请求是一门开发语言里比较常用和重要的功能,主要用于资源访问、接口数据请求和提交、上传下载文件等等操作,Http请求方式主要有:GET、POST、HEAD、PUT、DELETE、TRACE、CONNECT、OPTIONS。本文主要GET和POST这两种常用请求在Flutter中的用法,其中对POST将进行着重讲解。Flutter的Http网络请求的实现主要分为三种:io.dart里的HttpClient、Dart原生http请求和第三方库实现。
Http网络请求是互联网开发的基础协议,Http支持的请求方式有:GET、POST、HEAD、PUT、DELETE、TRACE、CONNECT、OPTIONS这八种。
GET请求
GET请求主要是执行获取资源操作的,例如通过URL从服务器获取返回的资源,其中GET可以把请求的一些参数信息拼接在URL上,传递给服务器,由服务器端进行参数信息解析,服务器收到请求后返回相应的资源给请求者。注意:GET请求拼接的URL数据大小和长度是有最大限制的,传输的数据量一般限制在2KB。
POST请求
POST请求主要用于执行提交信息、请求信息等操作,相比GET请求,POST请求的可以携带更多的数据,而且格式不限,如JSON、XML、文本等等都支持。并且POST传递的一些数据和参数不是直接拼接在URL后的,而是放在Http请求Body里,相对GET来说比较安全。并且传递的数据大小和格式是无限制的。
POST请求方式是一种比较常用网络请求方式,通常由请求头(header)和请求体(body)两部分组成。POST请求常见的请求体(body)有三种传输内容类型Content-type:application/x-www-form-urlencoded、application/json和multipart/form-data,当然还有其他的几种,不过不常用,常用的就是这三种。
HEAD请求
HEAD请求主要用于给请求的客户端返回头信息,而不返回Body主体内容。和GET方式类似,只不过GET方式有Body实体返回,而HEAD只返回头信息,无Body实体内容返回。主要是用于确认URL的有效性、资源更新的日期时间、查看服务器状态等等,对于有这方面需求的请求来说,比较不占用资源。
PUT请求
PUT请求主要用于执行传输文件操作,类似于FTP的文件上传一样,请求里包含文件内容,并将此文件保存到URI指定的服务器位置。
和POST方式的主要区别是:PUT请求方式如果前后两个请求相同,则后一个请求会把前一个请求覆盖掉,实现了PUT方式的修改资源;而POST请求方式如果前后两个请求相同,则后一个请求不会把前一个请求覆盖掉,实现了POST的增加资源。
DELETE请求
DELETE请求主要用于执行删除操作,告诉服务器想要删除的资源,不常用。
OPTIONS请求
OPTIONS请求主要用于执行查询针对所要请求的URI资源服务器所支持的请求方式,也就是获取这个URI所支持客户端提交给服务器端的请求方式有哪些。
TRACE请求
TRACE请求主要用于执行追踪传输路径的操作,例如,我们发起了一个Http请求,在这个过程中这个请求可能会经过很多个路径和过程,TRACE就是告诉服务器在收到请求后,返回一条响应信息,将它收到的原始Http请求信息返回给客户端,这样就可以验证在Http传输过程中请求是否被修改过。
CONNECT请求
CONNECT请求主要用于执行连接代理操作,例如“翻墙”。客户端通过CONNECT方式与服务器建立通信隧道,进行TCP通信。主要通过SSL和TLS安全传输数据。CONNECT的作用就是告诉服务器让它代替客户端去请求访问某个资源,然后再将数据返回给客户端,相当于一个媒介中转。
Dart的Http请求
Dart原生http请求库是Dart提供的一种请求方式,常见的请求方式都支持,除此之外,还支持如上传和下载文件等操作。
Dart官方仓库提供了大量的三方库和官方库,引用也非常的方便,Dart PUB官方地址为:https://pub.dartlang.org,如下图所示:
1.1 安装依赖
使用Dart的原生http库进行网络请求时,需要先在Dart PUB或官方Github里把相关的http库引用下来,然后才能使用。添加包依赖前,我们可以使用https://pub.dev/packages/http...。
然后,在pubspec.yaml文件的dependencies节点添加http库依赖,如下所示:
http: ^0.12.0+2
然后,使用flutter packages get命令拉取库依赖。使用http进行网络请求前,需要先导入http包,如下:
import 'package:http/http.dart' as http;
1.2 常用方法
http库支持常见的get、post、del等请求。其中,get请求的格式如下:
get(dynamic url, { Map<String, String> headers }) → Future<Response>
- (必须)url:请求地址
- (可选)headers:请求头
post请求的格式如下:
post(dynamic url, { Map<String, String> headers, dynamic body, Encoding encoding }) → Future<Response>
- (必须)url:请求地址
- (可选)headers:请求头
- (可选)body:参数
- (编码)Encoding:编码
例如,下面是post的示例:
http.post('https://flutter-cn.firebaseio.com/products.json',
body: json.encode(param),encoding: Utf8Codec())
.then((http.Response response) {
final Map<String, dynamic> responseData = json.decode(response.body);
// 处理响应数据
}).catchError((error) {
print('$error错误');
});
1.3 示例
例如,下面使用Dart的http库实现get请求的示例,示例代码如下:
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() => runApp(MyApp());
var hotMovies =
'https://api.douban.com/v2/movie/in_theaters?apikey=0df993c66c0c636e29ecbb5344252a4a';
class MyApp extends StatelessWidget {
var movies = '';
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'http请求示例',
theme: new ThemeData(
primaryColor: Colors.white,
),
home: new Scaffold(
appBar: new AppBar(
title: new Text('http请求示例'),
),
body: new Column(children: <Widget>[
new RaisedButton(
child: new Text('获取电影列表'), onPressed: getFilmList()),
new Expanded(
child: new Text('$movies'),
)
]),
));
}
getFilmList() {
http.get(hotMovies).then((response) {
movies = response.body;
});
}
}
运行上面的代码,结果如下图:
除了get请求,http的post请求示例如下:
import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:http_parser/http_parser.dart';
class DartHttpUtils {
//创建client实例
var _client = http.Client();
//发送GET请求
getClient() async {
var url = "https://abc.com:8090/path1?name=abc&pwd=123";
_client.get(url).then((http.Response response) {
//处理响应信息
if (response.statusCode == 200) {
print(response.body);
} else {
print('error');
}
});
}
//发送POST请求,application/x-www-form-urlencoded
postUrlencodedClient() async {
var url = "https://abc.com:8090/path2";
//设置header
Map<String, String> headersMap = new Map();
headersMap["content-type"] = "application/x-www-form-urlencoded";
//设置body参数
Map<String, String> bodyParams = new Map();
bodyParams["name"] = "value1";
bodyParams["pwd"] = "value2";
_client
.post(url, headers: headersMap, body: bodyParams, encoding: Utf8Codec())
.then((http.Response response) {
if (response.statusCode == 200) {
print(response.body);
} else {
print('error');
}
}).catchError((error) {
print('error');
});
}
//发送POST请求,application/json
postJsonClient() async {
var url = "https://abc.com:8090/path3";
Map<String, String> headersMap = new Map();
headersMap["content-type"] = ContentType.json.toString();
Map<String, String> bodyParams = new Map();
bodyParams["name"] = "value1";
bodyParams["pwd"] = "value2";
_client
.post(url,
headers: headersMap,
body: jsonEncode(bodyParams),
encoding: Utf8Codec())
.then((http.Response response) {
if (response.statusCode == 200) {
print(response.body);
} else {
print('error');
}
}).catchError((error) {
print('error');
});
}
// 发送POST请求,multipart/form-data
postFormDataClient() async {
var url = "https://abc.com:8090/path4";
var client = new http.MultipartRequest("post", Uri.parse(url));
client.fields["name"] = "value1";
client.fields["pwd"] = "value2";
client.send().then((http.StreamedResponse response) {
if (response.statusCode == 200) {
response.stream.transform(utf8.decoder).join().then((String string) {
print(string);
});
} else {
print('error');
}
}).catchError((error) {
print('error');
});
}
// 发送POST请求,multipart/form-data,上传文件
postFileClient() async {
var url = "https://abc.com:8090/path5";
var client = new http.MultipartRequest("post", Uri.parse(url));
http.MultipartFile.fromPath('file', 'sdcard/img.png',
filename: 'img.png', contentType: MediaType('image', 'png'))
.then((http.MultipartFile file) {
client.files.add(file);
client.fields["description"] = "descriptiondescription";
client.send().then((http.StreamedResponse response) {
if (response.statusCode == 200) {
response.stream.transform(utf8.decoder).join().then((String string) {
print(string);
});
} else {
response.stream.transform(utf8.decoder).join().then((String string) {
print(string);
});
}
}).catchError((error) {
print(error);
});
});
}
///其余的HEAD、PUT、DELETE请求用法类似,大同小异,大家可以自己试一下
///在Widget里请求成功数据后,使用setState来更新内容和状态即可
///setState(() {
/// ...
/// });
}
HttpClient请求
Dart IO库中提供的HttpClient可以实现一些基本的Http请求。不过,HttpClient只能实现一些基本的网络请求,对应一些复杂的网络请求还无法完成,如POST里的Body请求体传输内容类型部分还无法支持,multipart/form-data这个类型传输还不支持。
2.1 使用方法
使用HttpClient发起请求主要分为五步:
1,创建一个HttpClient。
HttpClient httpClient = new HttpClient();
2,打开Http连接,设置请求头。
HttpClientRequest request = await httpClient.getUrl(uri);
在这一步,我们可以使用任意Http method,如httpClient.post(...)、httpClient.delete(...)等。如果包含Query参数,可以在构建uri时添加,如:
Uri uri=Uri(scheme: "https", host: "flutterchina.club", queryParameters: {
"xx":"xx",
"yy":"dd"
});
如果需要设置请求头,可以通过HttpClientRequest设置请求header,如:
request.headers.add("user-agent", "test");
如果是post或put等可以携带请求体的请求,还可以通过HttpClientRequest对象发送request body,如:
String payload="...";
request.add(utf8.encode(payload));
//request.addStream(_inputStream); //可以直接添加输入流
3,等待连接服务器。
HttpClientResponse response = await request.close();
到这一步之后,请求信息就已经发送给服务器了,返回一个HttpClientResponse对象,它包含响应头(header)和响应流(响应体的Stream),接下来就可以通过读取响应流来获取响应内容。
4,读取响应内容
String responseBody = await response.transform(utf8.decoder).join();
5,请求结束后,还需要关闭HttpClient。
httpClient.close();
2.2 请求示例
import 'package:flutter/material.dart';
import 'dart:convert';
import 'dart:io';
void main() => runApp(MyApp());
var hotMovies =
'https://api.douban.com/v2/movie/in_theaters?apikey=0df993c66c0c636e29ecbb5344252a4a';
class MyApp extends StatelessWidget {
var movies = '';
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'HttpClient请求示例',
theme: new ThemeData(
primaryColor: Colors.white,
),
home: new Scaffold(
appBar: new AppBar(
title: new Text('HttpClient请求示例'),
),
body: new Column(children: <Widget>[
new RaisedButton(
child: new Text('获取电影列表'), onPressed: getFilmList),
new Expanded(
child: new Text('$movies'),
)
]),
));
}
void getFilmList() async {
try {
HttpClient httpClient = new HttpClient();
HttpClientRequest request = await httpClient.getUrl(Uri.parse(hotMovies));
HttpClientResponse response = await request.close();
var result = await response.transform(utf8.decoder).join();
movies = result;
print('movies'+result);
httpClient.close();
}catch(e){
print('请求失败:$e');
}
}
}
执行上面的代码,结果如下图:
dio库
除了上面两种常见的请求方式外,Flutter开发中还可以使用dio等第三方库来实现Http网络请求,如Dart社区提供的dio库。
前面说过,HttpClient发起网络请求是比较麻烦的,很多事情都需要我们手动处理,如果再涉及到文件上传/下载、Cookie管理等就会非常繁琐。而Dart社区有一些第三方http请求库,就可以简化这些操作。dio库不仅支持常见的网络请求,还支持Restful API、FormData、拦截器、请求取消、Cookie管理、文件上传/下载、超时等操作。
3.1 安装依赖
和使用其他的第三方库一样,使用dio库之前需要先安装依赖,安装前可以在Dart PUB上搜索dio,确定其版本号,如下所示:
dependencies:
dio: 2.1.x #latest version
然后,执行flutter packages get命令或者点击【Packages get】选项拉取库依赖。
使用dio之前需要先导入dio库,并创建dio实例,如下所示:
import 'package:dio/dio.dart';
Dio dio = new Dio();
接下来,就可以通过 dio实例来发起网络请求了,注意,一个dio实例可以发起多个http请求,一般来说,APP只有一个http数据源时,dio应该使用单例模式。
3.2 使用方法
3.2.1 GET请求
import 'package:dio/dio.dart';
void getHttp() async {
try {
Response response;
response=await dio.get("/test?id=12&name=wendu")
print(response.data.toString());
} catch (e) {
print(e);
}
}
在上面的示例中,我们可以将query参数通过对象来传递,上面的代码等同于:
response=await dio.get("/test",queryParameters:{"id":12,"name":"wendu"})
print(response);
3.2.2 POST请求
response=await dio.post("/test",data:{"id":12,"name":"wendu"})
3.2.3 多个并发请求
如果要发起多个并发请求,可以使用下面的方式:
response= await Future.wait([dio.post("/info"),dio.get("/token")]);
3.2.4 下载文件
如果要下载文件,可以使用dio的download函数,如下所示:
response=await dio.download("https://www.google.com/",_savePath);
3.2.5 FormData请求
如果要发起表单请求,可以使用下面的方式:
FormData formData = new FormData.from({
"name": "wendux",
"age": 25,
});
response = await dio.post("/info", data: formData)
如果发送的数据是FormData,则dio会将请求header的contentType设为“multipart/form-data”。
当然,FormData也支持上传多个文件操作,例如:
FormData formData = new FormData.from({
"name": "wendux",
"age": 25,
"file1": new UploadFileInfo(new File("./upload.txt"), "upload1.txt"),
"file2": new UploadFileInfo(new File("./upload.txt"), "upload2.txt"),
// 支持文件数组上传
"files": [
new UploadFileInfo(new File("./example/upload.txt"), "upload.txt"),
new UploadFileInfo(new File("./example/upload.txt"), "upload.txt")
]
});
response = await dio.post("/info", data: formData)
3.2.6 回调设置
值得一提的是,dio内部仍然使用HttpClient发起的请求,所以代理、请求认证、证书校验等和HttpClient是相同的,我们可以在onHttpClientCreate回调中进行设置,例如:
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) {
//设置代理
client.findProxy = (uri) {
return "PROXY 192.168.1.2:8888";
};
//校验证书
httpClient.badCertificateCallback=(X509Certificate cert, String host, int port){
if(cert.pem==PEM){
return true; //证书一致,则允许发送数据
}
return false;
};
};
3.3 示例
import 'package:flutter/material.dart';
import 'package:dio/dio.dart';
void main() => runApp(MyApp());
var hotMovies = 'http://api.douban.com/v2/movie/in_theaters?apikey=0df993c66c0c636e29ecbb5344252a4a';
class MyApp extends StatelessWidget {
var movies = '';
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Dio请求示例',
theme: new ThemeData(
primaryColor: Colors.white,
),
home: new Scaffold(
appBar: new AppBar(
title: new Text('Dio请求示例'),
),
body: new Column(children: <Widget>[
new RaisedButton(
child: new Text('获取电影列表'), onPressed: getFilmList),
new Expanded(
child: new Text('$movies'),
)
]),
));
}
void getFilmList() async {
Dio dio = new Dio();
Response response=await dio.get(hotMovies);
movies=response.toString();
print('电影数据:'+movies);
}
}
综合示例
为了对前面的知识做一个简单的总结,下面通过一个见得的示例来讲解Flutter的基本使用,最终效果如图:
需要说的是,最新版本豆瓣api需要传递apikey才能获取值,下面是电影列表的源码:
import 'package:flutter/material.dart';
import 'dart:convert' as Convert;
import 'dart:io';
import 'package:flutter/cupertino.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '豆瓣电影',
home: Scaffold(
appBar: new AppBar(
title: new Text('豆瓣电影列表'),
),
body: DouBanListView(),),
);
}
}
class DouBanListView extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return DouBanState();
}
}
class DouBanState extends State<DouBanListView> with AutomaticKeepAliveClientMixin{
var url='http://api.douban.com/v2/movie/top250?start=25&count=10&apikey=0df993c66c0c636e29ecbb5344252a4a';
var subjects = [];
var itemHeight = 150.0;
requestMovieTop() async {
var httpClient = new HttpClient();
var request = await httpClient.getUrl(Uri.parse(url));
var response = await request.close();
var responseBody = await response.transform(Convert.utf8.decoder).join();
Map data = Convert.jsonDecode(responseBody);
setState(() {
subjects = data['subjects'];
});
}
@override
void initState() {
super.initState();
requestMovieTop();
}
@override
Widget build(BuildContext context) {
return Container(
child: getListViewContainer(),
);
}
getListViewContainer() {
if (subjects.length == 0) {
//loading
return CupertinoActivityIndicator();
}
return ListView.builder(
//item 的数量
itemCount: subjects.length,
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
//Flutter 手势处理
child: Container(
color: Colors.transparent,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
numberWidget(index + 1),
getItemContainerView(subjects[index]),
//下面的灰色分割线
Container(
height: 10,
color: Color.fromARGB(255, 234, 233, 234),
)
],
),
),
onTap: () {
//监听点击事件
print("click item index=$index");
},
);
});
}
//肖申克的救赎(1993) View
getTitleView(subject) {
var title = subject['title'];
var year = subject['year'];
return Container(
child: Row(
children: <Widget>[
Icon(
Icons.play_circle_outline,
color: Colors.redAccent,
),
Text(
title,
style: TextStyle(
fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black),
),
Text('($year)',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.grey))
],
),
);
}
getItemContainerView(var subject) {
var imgUrl = subject['images']['medium'];
return Container(
width: double.infinity,
padding: EdgeInsets.all(5.0),
child: Row(
children: <Widget>[
getImage(imgUrl),
Expanded(
child: getMovieInfoView(subject),
flex: 1,
)
],
),
);
}
//圆角图片
getImage(var imgUrl) {
return Container(
decoration: BoxDecoration(
image:
DecorationImage(image: NetworkImage(imgUrl), fit: BoxFit.cover),
borderRadius: BorderRadius.all(Radius.circular(5.0))),
margin: EdgeInsets.only(left: 8, top: 3, right: 8, bottom: 3),
height: itemHeight,
width: 100.0,
);
}
getStaring(var stars) {
return Row(
children: <Widget>[RatingBar(stars), Text('$stars')],
);
}
//电影标题,星标评分,演员简介Container
getMovieInfoView(var subject) {
var start = subject['rating']['average'];
return Container(
height: itemHeight,
alignment: Alignment.topLeft,
child: Column(
children: <Widget>[
getTitleView(subject),
RatingBar(start),
DescWidget(subject)
],
),
);
}
//NO.1 图标
numberWidget(var no) {
return Container(
child: Text(
'No.$no',
style: TextStyle(color: Color.fromARGB(255, 133, 66, 0)),
),
decoration: BoxDecoration(
color: Color.fromARGB(255, 255, 201, 129),
borderRadius: BorderRadius.all(Radius.circular(5.0))),
padding: EdgeInsets.fromLTRB(8, 4, 8, 4),
margin: EdgeInsets.only(left: 12, top: 10),
);
}
@override
bool get wantKeepAlive => true;
}
//类别、演员介绍
class DescWidget extends StatelessWidget {
var subject;
DescWidget(this.subject);
@override
Widget build(BuildContext context) {
var casts = subject['casts'];
var sb = StringBuffer();
var genres = subject['genres'];
for (var i = 0; i < genres.length; i++) {
sb.write('${genres[i]} ');
}
sb.write("/ ");
List<String> list = List.generate(
casts.length, (int index) => casts[index]['name'].toString());
for (var i = 0; i < list.length; i++) {
sb.write('${list[i]} ');
}
return Container(
alignment: Alignment.topLeft,
child: Text(
sb.toString(),
softWrap: true,
textDirection: TextDirection.ltr,
style:
TextStyle(fontSize: 16, color: Color.fromARGB(255, 118, 117, 118)),
),
);
}
}
class RatingBar extends StatelessWidget {
double stars;
RatingBar(this.stars);
@override
Widget build(BuildContext context) {
List<Widget> startList = [];
//实心星星
var startNumber = stars ~/ 2;
//半实心星星
var startHalf = 0;
if (stars.toString().contains('.')) {
int tmp = int.parse((stars.toString().split('.')[1]));
if (tmp >= 5) {
startHalf = 1;
}
}
//空心星星
var startEmpty = 5 - startNumber - startHalf;
for (var i = 0; i < startNumber; i++) {
startList.add(Icon(
Icons.star,
color: Colors.amberAccent,
size: 18,
));
}
if (startHalf > 0) {
startList.add(Icon(
Icons.star_half,
color: Colors.amberAccent,
size: 18,
));
}
for (var i = 0; i < startEmpty; i++) {
startList.add(Icon(
Icons.star_border,
color: Colors.grey,
size: 18,
));
}
startList.add(Text(
'$stars',
style: TextStyle(
color: Colors.grey,
),
));
return Container(
alignment: Alignment.topLeft,
padding: const EdgeInsets.only(left: 0, top: 8, right: 0, bottom: 5),
child: Row(
children: startList,
),
);
}
}
附:
1,Flutter系列教程之环境搭建
2,Flutter系列教程之学习线路
3,Flutter系列教程之Dart语法
4,Flutter系列教程之快速入门
5,Flutter系列教程之Flutter 1.7新特性
6,通过HttpClient发起HTTP请求
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。