簡介
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官方地址為: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請求